[gradle-1.12] 63/211: Imported Upstream version 1.3

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:18:05 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 586da5d8a4131891c68f20ae50fcf83c2e9ce0ef
Author: Damien Raude-Morvan <drazzib at debian.org>
Date:   Sun Nov 25 19:35:16 2012 +0100

    Imported Upstream version 1.3
---
 build.gradle                                       | 510 +++------------
 buildSrc/build.gradle                              |   7 +-
 .../main/groovy/org/gradle/build/BuildTypes.groovy |  77 +++
 .../src/main/groovy/org/gradle/build/JarJar.groovy |  31 +-
 .../gradle/build/docs/BuildableDOMCategory.groovy  |   3 +-
 .../build/docs/UserGuideTransformTask.groovy       |   4 +-
 .../gradle/build/docs/dsl/ClassLinkMetaData.java   | 164 -----
 .../build/docs/dsl/ExtractDslMetaDataTask.groovy   | 144 -----
 .../org/gradle/build/docs/dsl/LinkMetaData.java    |  44 --
 .../build/docs/dsl/SourceMetaDataVisitor.java      | 496 --------------
 .../gradle/build/docs/dsl/TypeNameResolver.java    | 167 -----
 .../docs/dsl/docbook/AssembleDslDocTask.groovy     |  47 +-
 .../build/docs/dsl/docbook/BasicJavadocLexer.java  |   1 +
 .../docs/dsl/docbook/BlockDetailRenderer.java      |  87 +++
 .../gradle/build/docs/dsl/docbook/BlockDoc.groovy  |  69 --
 .../build/docs/dsl/docbook/BlockTableRenderer.java |  76 +++
 .../build/docs/dsl/docbook/BlocksRenderer.java     |  91 +++
 .../docs/dsl/docbook/ClassDescriptionRenderer.java |  62 ++
 .../gradle/build/docs/dsl/docbook/ClassDoc.groovy  | 343 ----------
 .../build/docs/dsl/docbook/ClassDocBuilder.java    |  50 ++
 .../docs/dsl/docbook/ClassDocCommentBuilder.java   |  36 ++
 .../dsl/docbook/ClassDocExtensionsBuilder.java     | 106 +++
 .../docs/dsl/docbook/ClassDocMethodsBuilder.java   |  92 +++
 .../dsl/docbook/ClassDocPropertiesBuilder.java     | 130 ++++
 .../build/docs/dsl/docbook/ClassDocRenderer.groovy | 443 -------------
 .../build/docs/dsl/docbook/ClassDocRenderer.java   |  61 ++
 .../docs/dsl/docbook/ClassDocSuperTypeBuilder.java |  69 ++
 .../docs/dsl/docbook/ClassExtensionDoc.groovy      |  95 ---
 .../build/docs/dsl/docbook/DocBookBuilder.java     |  93 +++
 .../gradle/build/docs/dsl/docbook/DocComment.java  |   6 +-
 .../build/docs/dsl/docbook/DslDocModel.groovy      |  76 ++-
 .../build/docs/dsl/docbook/DslElementDoc.java      |  33 -
 .../docs/dsl/docbook/ElementWarningsRenderer.java  |  52 ++
 .../docbook/ExtensionBlocksSummaryRenderer.java    |  68 ++
 .../docbook/ExtensionMethodsSummaryRenderer.java   |  68 ++
 .../ExtensionPropertiesSummaryRenderer.java        |  68 ++
 .../docs/dsl/docbook/ExtraAttributeDoc.groovy      |  45 --
 .../docs/dsl/docbook/HtmlToXmlJavadocLexer.java    | 110 +++-
 .../build/docs/dsl/docbook/JavadocConverter.java   | 287 ++++-----
 .../build/docs/dsl/docbook/JavadocLexer.java       |   3 +
 .../docs/dsl/docbook/JavadocLinkConverter.java     |   8 +-
 .../build/docs/dsl/docbook/LinkRenderer.java       |   4 +-
 .../docs/dsl/docbook/MethodDetailRenderer.java     |  69 ++
 .../gradle/build/docs/dsl/docbook/MethodDoc.groovy |  68 --
 .../docs/dsl/docbook/MethodTableRenderer.java      |  86 +++
 .../build/docs/dsl/docbook/MethodsRenderer.java    |  91 +++
 .../docs/dsl/docbook/ModelBuilderSupport.java      |  63 ++
 .../build/docs/dsl/docbook/PropertiesRenderer.java |  91 +++
 .../docs/dsl/docbook/PropertyDetailRenderer.java   |  81 +++
 .../build/docs/dsl/docbook/PropertyDoc.groovy      |  81 ---
 .../docs/dsl/docbook/PropertyTableRenderer.java    |  76 +++
 .../docs/dsl/docbook/ReferencedTypeBuilder.java    |  40 ++
 .../build/docs/dsl/docbook/model/BlockDoc.groovy   |  73 +++
 .../build/docs/dsl/docbook/model/ClassDoc.groovy   | 176 +++++
 .../dsl/docbook/model/ClassExtensionDoc.groovy     |  82 +++
 .../docbook/model/ClassExtensionMetaData.groovy    |  34 +
 .../docs/dsl/docbook/model/DslElementDoc.java      |  33 +
 .../dsl/docbook/model/ExtensionMetaData.groovy     |  28 +
 .../dsl/docbook/model/ExtraAttributeDoc.groovy     |  46 ++
 .../build/docs/dsl/docbook/model/MethodDoc.groovy  |  74 +++
 .../docs/dsl/docbook/model/MixinMetaData.groovy    |  26 +
 .../docs/dsl/docbook/model/PropertyDoc.groovy      |  91 +++
 .../build/docs/dsl/links/ClassLinkMetaData.java    | 164 +++++
 .../gradle/build/docs/dsl/links/LinkMetaData.java  |  44 ++
 .../docs/dsl/model/AbstractLanguageElement.java    |  65 --
 .../docs/dsl/model/ClassExtensionMetaData.groovy   |  34 -
 .../gradle/build/docs/dsl/model/ClassMetaData.java | 262 --------
 .../build/docs/dsl/model/ExtensionMetaData.groovy  |  28 -
 .../build/docs/dsl/model/LanguageElement.java      |  28 -
 .../build/docs/dsl/model/MethodMetaData.java       | 142 ----
 .../build/docs/dsl/model/MixinMetaData.groovy      |  26 -
 .../build/docs/dsl/model/ParameterMetaData.java    |  55 --
 .../build/docs/dsl/model/PropertyMetaData.java     | 111 ----
 .../gradle/build/docs/dsl/model/TypeContainer.java |  22 -
 .../gradle/build/docs/dsl/model/TypeMetaData.java  | 186 ------
 .../docs/dsl/source/ExtractDslMetaDataTask.groovy  | 144 +++++
 .../docs/dsl/source/SourceMetaDataVisitor.java     | 496 ++++++++++++++
 .../build/docs/dsl/source/TypeNameResolver.java    | 167 +++++
 .../dsl/source/model/AbstractLanguageElement.java  |  65 ++
 .../build/docs/dsl/source/model/ClassMetaData.java | 283 ++++++++
 .../docs/dsl/source/model/LanguageElement.java     |  28 +
 .../docs/dsl/source/model/MethodMetaData.java      | 135 ++++
 .../docs/dsl/source/model/ParameterMetaData.java   |  56 ++
 .../docs/dsl/source/model/PropertyMetaData.java    | 104 +++
 .../build/docs/dsl/source/model/TypeContainer.java |  22 +
 .../build/docs/dsl/source/model/TypeMetaData.java  | 193 ++++++
 .../docs/model/SimpleClassMetaDataRepository.java  |   1 +
 .../build/integtest/IntegTestConvention.groovy     |  49 --
 .../gradle/build/integtest/IntegTestPlugin.groovy  |  26 -
 .../gradle/plugins/jsoup/JsoupFilterReader.groovy  |  58 --
 .../org/gradle/build/docs/XmlSpecification.groovy  |   9 +-
 .../docs/dsl/ExtractDslMetaDataTaskTest.groovy     | 717 ---------------------
 .../build/docs/dsl/TypeNameResolverTest.groovy     | 178 -----
 .../docs/dsl/docbook/BasicJavadocLexerTest.groovy  |  12 +
 .../docbook/ClassDocExtensionsBuilderTest.groovy   | 180 ++++++
 .../dsl/docbook/ClassDocMethodsBuilderTest.groovy  | 193 ++++++
 .../docbook/ClassDocPropertiesBuilderTest.groovy   | 170 +++++
 .../docs/dsl/docbook/ClassDocRendererTest.groovy   | 421 ++++++++----
 .../build/docs/dsl/docbook/ClassDocTest.groovy     | 367 -----------
 .../dsl/docbook/HtmlToXmlJavadocLexerTest.groovy   | 171 +++++
 .../docs/dsl/docbook/JavadocConverterTest.groovy   |  35 +-
 .../dsl/docbook/JavadocLinkConverterTest.groovy    |   6 +-
 .../build/docs/dsl/docbook/LinkRendererTest.groovy |   6 +-
 .../build/docs/dsl/model/ClassMetaDataTest.groovy  |  41 --
 .../build/docs/dsl/model/MethodMetaDataTest.groovy | 163 -----
 .../docs/dsl/model/ParameterMetaDataTest.groovy    |  30 -
 .../docs/dsl/model/PropertyMetaDataTest.groovy     | 134 ----
 .../build/docs/dsl/model/TypeMetaDataTest.groovy   | 183 ------
 .../dsl/source/ExtractDslMetaDataTaskTest.groovy   | 717 +++++++++++++++++++++
 .../docs/dsl/source/TypeNameResolverTest.groovy    | 178 +++++
 .../docs/dsl/source/model/ClassMetaDataTest.groovy |  41 ++
 .../dsl/source/model/MethodMetaDataTest.groovy     | 151 +++++
 .../dsl/source/model/ParameterMetaDataTest.groovy  |  30 +
 .../dsl/source/model/PropertyMetaDataTest.groovy   | 122 ++++
 .../docs/dsl/source/model/TypeMetaDataTest.groovy  | 183 ++++++
 config/checkstyle/checkstyle-groovy.xml            |   6 +
 config/checkstyle/checkstyle.xml                   |   5 +-
 config/checkstyle/suppressions.xml                 |   4 +
 gradle/buildReceipt.gradle                         | 127 ++++
 gradle/classycle.gradle                            |  61 +-
 gradle/compile.gradle                              |   2 +
 gradle/conventions-dsl.gradle                      |  18 -
 gradle/dependencies.gradle                         | 145 +++++
 gradle/eclipse.gradle                              |  24 +-
 gradle/groovyProject.gradle                        |  70 +-
 gradle/idea.gradle                                 |  29 +-
 gradle/incomingDistributions.gradle                |  37 ++
 gradle/intTestImage.gradle                         |  47 ++
 gradle/integTest.gradle                            | 104 ++-
 ...oDependencyResolutionDuringConfiguration.gradle |  14 +
 gradle/publish.gradle                              |  10 +-
 gradle/testWithUnknownOS.gradle                    |   1 +
 gradle/versioning.gradle                           |  15 +-
 gradle/wrapper.gradle                              |  40 ++
 gradle/wrapper/gradle-wrapper.properties           |   4 +-
 gradlew                                            |   4 +-
 settings.gradle                                    |   8 +-
 subprojects/announce/announce.gradle               |   4 +-
 .../internal/AnnouncerUnavailableException.groovy  |  29 -
 .../internal/AnnouncerUnavailableException.java    |  29 +
 .../base-services-groovy.gradle                    |  24 +
 .../gradle/api/InvalidActionClosureException.java  |  67 ++
 .../main/groovy/org/gradle/api/package-info.java   |  26 +
 .../main/groovy/org/gradle/api/specs/AndSpec.java  |  62 ++
 .../org/gradle/api/specs/internal/ClosureSpec.java |   0
 .../groovy/org/gradle/api/specs/package-info.java  |  20 +
 .../api/internal/ClosureBackedActionTest.groovy    |  91 +++
 .../api/specs/AbstractCompositeSpecTest.java       |  71 ++
 .../groovy/org/gradle/api/specs/AndSpecTest.java   |  57 ++
 .../groovy/org/gradle/api/specs/NotSpecTest.java   |  37 ++
 .../groovy/org/gradle/api/specs/OrSpecTest.java    |  46 ++
 subprojects/base-services/base-services.gradle     |   3 +-
 .../src/main/java/org/gradle/api/Experimental.java |  30 -
 .../main/java}/org/gradle/api/GradleException.java |   0
 .../src/main/java/org/gradle/api/Incubating.java   |  30 +
 .../src/main/java/org/gradle/api/JavaVersion.java  |   5 +-
 .../src/main/java}/org/gradle/api/Named.java       |   0
 .../src/main/java}/org/gradle/api/Namer.java       |   0
 .../src/main/java}/org/gradle/api/Transformer.java |   0
 .../java/org/gradle/api/UncheckedIOException.java  |  36 ++
 .../main/java/org/gradle/api/internal/Actions.java | 174 +++++
 .../main/java/org/gradle/api/internal/Cast.java    |  46 ++
 .../org/gradle/api/internal/ErroringAction.java    |  44 ++
 .../gradle/api/internal/HasInternalProtocol.java   |  35 +
 .../java/org/gradle/api/internal/IoActions.java    |  93 +++
 .../java/org/gradle/api/internal/Transformers.java |  95 +++
 .../java/org/gradle/api/specs/CompositeSpec.java   |  69 ++
 .../main/java/org/gradle/api/specs/NotSpec.java    |  34 +
 .../src/main/java/org/gradle/api/specs/OrSpec.java |  49 ++
 .../src/main/java}/org/gradle/api/specs/Spec.java  |   0
 .../org/gradle/internal/CompositeStoppable.java    |  11 +-
 .../main/java/org/gradle/internal/Factories.java   |  27 +
 .../java/org/gradle/internal/SystemProperties.java |  40 +-
 .../internal/classpath/DefaultClassPath.java       |   5 +
 .../concurrent/DefaultExecutorFactory.java         |   2 +-
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |   8 -
 .../internal/reflect/DirectInstantiator.java       |   6 +-
 .../org/gradle/internal/reflect/Instantiator.java  |   4 +-
 .../reflect/ObjectInstantiationException.java      |  23 +
 .../internal/service/AbstractServiceRegistry.java  |  74 +++
 .../internal/service/DefaultServiceRegistry.java   |  14 +-
 .../gradle/internal/service/ServiceLocator.java    |  11 +-
 .../gradle/internal/service/ServiceRegistry.java   |  14 +-
 .../service/SynchronizedServiceRegistry.java       |  10 +
 .../main/java/org/gradle/util/CollectionUtils.java | 418 ++++++++++++
 .../groovy/org/gradle/api/JavaVersionSpec.groovy   |   2 +
 .../org/gradle/api/internal/ActionsTest.groovy     | 133 ++++
 .../groovy/org/gradle/api/internal/CastTest.groovy |  44 ++
 .../gradle/api/internal/ErroringActionTest.groovy  |  52 ++
 .../org/gradle/api/internal/IoActionsTest.groovy   |  94 +++
 .../gradle/api/internal/TransformersTest.groovy    |  74 +++
 .../org/gradle/internal/FactoriesTest.groovy       |  35 +
 .../gradle/internal/SystemPropertiesTest.groovy    |  27 +
 .../internal/reflect/DirectInstantiatorTest.groovy |  38 +-
 .../service/DefaultServiceRegistryTest.java        | 103 ++-
 .../org/gradle/util/CollectionUtilsTest.groovy     | 273 ++++++++
 .../build-comparison/build-comparison.gradle       |  37 ++
 .../gradle/BuildComparisonIntegrationSpec.groovy   | 307 +++++++++
 ...Pre12CompareGradleBuildsCrossVersionSpec.groovy | 178 +++++
 .../compareArchives/source/build.gradle            |   1 +
 .../compareArchives/source/settings.gradle         |   2 +
 .../source/src/main/java/org/gradle/Changed.java   |   8 +
 .../src/main/java/org/gradle/DifferentCrc.java     |   8 +
 .../src/main/java/org/gradle/SourceBuildOnly.java  |   3 +
 .../source/src/main/java/org/gradle/Unchanged.java |   8 +
 .../compareArchives/target/build.gradle            |   2 +
 .../compareArchives/target/settings.gradle         |   2 +
 .../target/src/main/java/org/gradle/Changed.java   |  10 +
 .../src/main/java/org/gradle/DifferentCrc.java     |   8 +
 .../src/main/java/org/gradle/TargetBuildOnly.java  |   3 +
 .../target/src/main/java/org/gradle/Unchanged.java |   8 +
 .../compare/internal/BuildComparator.java          |  32 +
 .../compare/internal/BuildComparisonResult.java    |  66 ++
 .../compare/internal/BuildComparisonSpec.java      |  60 ++
 .../internal/BuildComparisonSpecBuilder.java       |  35 +
 .../internal/BuildComparisonSpecFactory.java       |  65 ++
 .../compare/internal/BuildOutcomeComparator.java   |  45 ++
 .../internal/BuildOutcomeComparatorFactory.java    |  24 +
 .../internal/BuildOutcomeComparisonResult.java     |  44 ++
 .../BuildOutcomeComparisonResultSupport.java       |  38 ++
 .../compare/internal/ComparisonResultType.java     |  29 +
 .../compare/internal/DefaultBuildComparator.java   |  77 +++
 .../internal/DefaultBuildComparisonSpec.java       |  47 ++
 .../DefaultBuildComparisonSpecBuilder.java         |  55 ++
 .../DefaultBuildOutcomeComparatorFactory.java      |  41 ++
 .../gradle/CompareGradleBuilds.java                | 263 ++++++++
 .../gradle/CompareGradleBuildsPlugin.groovy        |  42 ++
 .../gradle/GradleBuildInvocationSpec.java          | 104 +++
 .../internal/ComparableGradleBuildExecuter.java    | 102 +++
 .../internal/DefaultGradleBuildInvocationSpec.java | 129 ++++
 .../gradle/internal/GradleBuildComparison.java     | 275 ++++++++
 .../internal/GradleBuildOutcomeSetInferrer.java    |  73 +++
 .../internal/GradleBuildOutcomeSetTransformer.java |  95 +++
 .../buildcomparison/gradle/package-info.java       |  20 +
 .../outcome/internal/BuildOutcome.java             |  33 +
 .../outcome/internal/BuildOutcomeAssociation.java  |  50 ++
 .../outcome/internal/BuildOutcomeAssociator.java   |  30 +
 .../outcome/internal/BuildOutcomeSupport.java      |  36 ++
 ...ypeAndCharacteristicBuildOutcomeAssociator.java |  49 ++
 .../ByTypeAndNameBuildOutcomeAssociator.java       |  29 +
 .../internal/CompositeBuildOutcomeAssociator.java  |  41 ++
 .../internal/DefaultBuildOutcomeAssociation.java   |  42 ++
 .../archive/GeneratedArchiveBuildOutcome.java      |  54 ++
 .../GeneratedArchiveBuildOutcomeComparator.java    |  91 +++
 ...neratedArchiveBuildOutcomeComparisonResult.java |  67 ++
 ...BuildOutcomeComparisonResultHtmlRenderer.groovy | 150 +++++
 ...GeneratedArchiveBuildOutcomeHtmlRenderer.groovy |  46 ++
 .../internal/archive/entry/ArchiveEntry.java       |  94 +++
 .../archive/entry/ArchiveEntryComparison.java      |  59 ++
 .../entry/FileToArchiveEntrySetTransformer.java    |  68 ++
 .../entry/ZipEntryToArchiveEntryTransformer.java   |  33 +
 .../internal/unknown/UnknownBuildOutcome.java      |  30 +
 .../unknown/UnknownBuildOutcomeComparator.java     |  32 +
 .../UnknownBuildOutcomeComparisonResult.java       |  31 +
 ...BuildOutcomeComparisonResultHtmlRenderer.groovy |  38 ++
 .../unknown/UnknownBuildOutcomeHtmlRenderer.groovy |  34 +
 .../internal/BuildComparisonResultRenderer.java    |  30 +
 .../BuildOutcomeComparisonResultRenderer.java      |  35 +
 ...uildOutcomeComparisonResultRendererFactory.java |  25 +
 .../render/internal/BuildOutcomeRenderer.java      |  35 +
 .../internal/BuildOutcomeRendererFactory.java      |  25 +
 ...uildOutcomeComparisonResultRendererFactory.java |  49 ++
 .../DefaultBuildOutcomeRendererFactory.java        |  50 ++
 ...BuildOutcomeComparisonResultHtmlRenderer.groovy |  34 +
 .../internal/html/BuildOutcomeHtmlRenderer.groovy  |  30 +
 .../GradleBuildComparisonResultHtmlRenderer.groovy | 303 +++++++++
 .../render/internal/html/HtmlRenderContext.java    |  73 +++
 .../compare-gradle-builds.properties               |   1 +
 .../internal/BuildComparisonSpecFactoryTest.groovy |  56 ++
 .../internal/DefaultBuildComparatorTest.groovy     |  86 +++
 ...DefaultBuildOutcomeComparatorFactoryTest.groovy |  94 +++
 .../DefaultGradleBuildInvocationSpecTest.groovy    |  61 ++
 .../GradleBuildOutcomeSetInferrerTest.groovy       |  95 +++
 .../GradleBuildOutcomeSetTransformerTest.groovy    | 114 ++++
 .../ByTypeAndNameBuildOutcomeAssociatorTest.groovy |  47 ++
 ...neratedArchiveBuildOutcomeComparatorTest.groovy | 129 ++++
 .../entry/ArchiveEntryComparisonTest.groovy        |  72 +++
 .../internal/archive/entry/ArchiveEntryTest.groovy |  51 ++
 .../FileToArchiveEntrySetTransformerTest.groovy    |  66 ++
 .../ZipEntryToArchiveEntryTransformerTest.groovy   |  63 ++
 ...tcomeComparisonResultRendererFactoryTest.groovy |  58 ++
 ...dleBuildComparisonResultHtmlRendererTest.groovy | 127 ++++
 .../fixtures/MutableDomainObjectSet.groovy         |  29 +
 .../fixtures/MutableProjectOutcomes.groovy         |  72 +++
 .../fixtures/ProjectOutcomesBuilder.groovy         |  32 +
 .../outcome/string/StringBuildOutcome.groovy       |  69 ++
 .../string/StringBuildOutcomeComparator.groovy     |  32 +
 .../StringBuildOutcomeComparisonResult.groovy      |  37 ++
 ...BuildOutcomeComparisonResultHtmlRenderer.groovy |  44 ++
 ...gBuildOutcomeComparisonResultMapRenderer.groovy |  36 ++
 .../string/StringBuildOutcomeHtmlRenderer.groovy   |  31 +
 subprojects/cli/cli.gradle                         |   6 +-
 .../java/org/gradle/cli/CommandLineOption.java     |  10 +-
 .../org/gradle/cli/ParsedCommandLineOption.java    |   6 +-
 .../org/gradle/cli/CommandLineParserTest.groovy    |  38 +-
 .../gradle/cli/ParsedCommandLineOptionSpec.groovy  |  62 ++
 subprojects/code-quality/code-quality.gradle       |   5 +-
 ...toTestedSampleCodeQualityIntegrationTest.groovy |  28 +
 .../quality/FindBugsPluginIntegrationTest.groovy   |  68 +-
 .../internal/FindBugsSpecBuilderTest.groovy        | 101 ++-
 .../gradle/api/plugins/quality/Checkstyle.groovy   |  15 +-
 .../org/gradle/api/plugins/quality/CodeNarc.groovy |  13 +-
 .../org/gradle/api/plugins/quality/FindBugs.groovy | 117 +++-
 .../api/plugins/quality/FindBugsExtension.groovy   |  59 +-
 .../api/plugins/quality/FindBugsPlugin.groovy      |   9 +-
 .../org/gradle/api/plugins/quality/JDepend.groovy  |  13 +-
 .../org/gradle/api/plugins/quality/Pmd.groovy      |  11 +-
 .../quality/internal/findbugs/FindBugsSpec.java    |  18 +-
 .../internal/findbugs/FindBugsSpecBuilder.java     | 117 +++-
 .../internal/findbugs/FindBugsWorkerManager.groovy |  15 +-
 .../internal/findbugs/FindBugsWorkerServer.java    |   2 +-
 .../api/plugins/quality/FindBugsPluginTest.groovy  |  62 +-
 .../gradle/api/plugins/quality/FindBugsTest.groovy |  78 +--
 subprojects/core-impl/core-impl.gradle             |  60 +-
 .../DefaultDependencyManagementServices.java       |  83 ++-
 .../artifacts/DefaultResolvedArtifact.java         |  31 +-
 .../ivyservice/ArtifactResolveResult.java          |   7 +
 .../artifacts/ivyservice/ArtifactResolver.java     |   2 +-
 .../ivyservice/BrokenArtifactResolveResult.java    |  36 --
 .../BrokenModuleVersionResolveResult.java          |  43 --
 .../ivyservice/BuildableArtifactResolveResult.java |  41 ++
 .../BuildableModuleVersionResolveResult.java       |  47 ++
 .../CacheLockingArtifactDependencyResolver.java    |  12 +-
 .../DefaultBuildableArtifactResolveResult.java     |  72 +++
 ...DefaultBuildableModuleVersionResolveResult.java |  86 +++
 .../ivyservice/DefaultCacheLockingManager.java     |   2 +-
 .../ivyservice/DefaultIvyDependencyPublisher.java  |   5 +-
 .../ivyservice/DefaultUnresolvedDependency.java    |   4 +-
 .../ivyservice/DependencyToModuleResolver.java     |   4 +-
 .../ErrorHandlingArtifactDependencyResolver.java   |  12 +-
 .../ivyservice/ErrorHandlingArtifactPublisher.java |  21 +-
 .../FileBackedArtifactResolveResult.java           |  36 --
 .../ForcedModuleVersionIdResolveResult.java        |  47 ++
 .../ivyservice/IvyBackedArtifactPublisher.java     | 182 +++---
 .../ivyservice/IvyModuleDescriptorWriter.java      |  30 +
 .../ivyservice/IvyXmlModuleDescriptorWriter.java   | 576 +++++++++++++++++
 .../ivyservice/ModuleVersionIdResolveResult.java   |  10 +
 .../ivyservice/ResolvedArtifactFactory.java        |   4 +-
 .../SelfResolvingDependencyResolver.java           |  94 +--
 ...cuitEmptyConfigsArtifactDependencyResolver.java |  34 +-
 .../VersionForcingDependencyToModuleResolver.java  |   3 +-
 .../clientmodule/ClientModuleResolver.java         |  41 +-
 .../AbstractDependencyResolverAdapter.java         |  83 +++
 .../BuildableModuleVersionDescriptor.java          |  59 ++
 .../CacheLockingModuleVersionRepository.java       |  19 +-
 .../ivyresolve/CachingModuleVersionRepository.java | 119 ++--
 .../DefaultBuildableModuleVersionDescriptor.java   |  92 +++
 .../ivyservice/ivyresolve/DefaultIvyAdapter.java   |   4 +-
 .../ivyresolve/DefaultModuleVersionDescriptor.java |  41 --
 .../ivyresolve/DependencyResolverAdapter.java      | 117 ----
 .../ivyresolve/DependencyResolverIdentifier.java   |   8 +-
 .../ivyservice/ivyresolve/DownloadedArtifact.java  |  43 --
 .../ExternalResourceResolverAdapter.java           |  54 ++
 .../ivyresolve/IvyDependencyResolverAdapter.java   |  64 ++
 .../ivyresolve/LazyDependencyToModuleResolver.java |  52 +-
 .../LocalAwareModuleVersionRepository.java         |  31 +
 .../ivyresolve/LocalModuleVersionRepository.java   |  53 ++
 .../ivyresolve/LoopbackDependencyResolver.java     |  13 +-
 .../ivyresolve/ModuleVersionDescriptor.java        |   3 +-
 .../ivyresolve/ModuleVersionRepository.java        |  19 +-
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |  32 +-
 .../StartParameterResolutionOverride.java          |   9 +-
 .../ivyservice/ivyresolve/UserResolverChain.java   | 147 +++--
 .../DownloadedIvyModuleDescriptorParser.java       |   2 +-
 .../parser/GradlePomModuleDescriptorBuilder.java   |  26 +-
 .../parser/GradlePomModuleDescriptorParser.java    |   9 +-
 .../modulecache/DefaultModuleDescriptorCache.java  |   5 +-
 .../modulecache/ModuleDescriptorFileStore.java     |  48 --
 .../modulecache/ModuleDescriptorStore.java         |  57 +-
 .../projectmodule/ProjectDependencyResolver.java   |  43 +-
 .../resolveengine/DefaultDependencyResolver.java   |  10 +-
 .../resolveengine/DependencyGraphBuilder.java      | 104 ++-
 .../LatestModuleConflictResolver.java              |  29 +-
 .../result/CachingDependencyResultFactory.java     |  54 ++
 .../result/InternalDependencyResult.java           |  32 +
 .../result/ModuleVersionSelection.java             |  30 +
 .../result/ResolutionResultBuilder.java            |  75 +++
 .../result/ResolvedConfigurationListener.java      |  30 +
 .../result/VersionSelectionReasons.java            |  51 ++
 .../DefaultLocalMavenRepositoryLocator.java        |  39 +-
 .../mvnsettings/DefaultMavenFileLocations.java     |   2 +-
 .../mvnsettings/DefaultMavenSettingsProvider.java  |  43 ++
 .../mvnsettings/MavenSettingsProvider.java         |  23 +
 .../AbstractAuthenticationSupportedRepository.java |   2 +-
 .../CustomResolverArtifactRepository.java          |  26 +-
 .../repositories/DefaultBaseRepositoryFactory.java | 138 ++++
 .../DefaultExternalResourceRepository.java         | 138 ----
 .../DefaultFlatDirArtifactRepository.java          |  25 +-
 .../repositories/DefaultIvyArtifactRepository.java |  32 +-
 .../DefaultMavenArtifactRepository.java            |  22 +-
 .../repositories/DefaultResolverFactory.java       | 117 ----
 .../EnhancedArtifactDownloadReport.java            |  38 --
 .../repositories/ExternalResourceRepository.java   |  46 --
 .../repositories/ExternalResourceResolver.java     | 471 --------------
 .../IvyArtifactRepositoryInternal.java             |  26 +
 .../artifacts/repositories/IvyResolver.java        |  47 --
 .../artifacts/repositories/MavenResolver.java      | 316 ---------
 .../repositories/PatternBasedResolver.java         |  33 -
 .../ProgressLoggingTransferListener.java           |  81 ---
 .../DownloadingRepositoryCacheManager.java         |  49 +-
 .../EnhancedArtifactDownloadReport.java            |  56 ++
 .../LocalFileRepositoryCacheManager.java           |   8 +-
 .../layout/GradleRepositoryLayout.java             |   2 +-
 .../repositories/layout/MavenRepositoryLayout.java |   2 +-
 .../layout/PatternRepositoryLayout.java            |   2 +-
 .../repositories/layout/RepositoryLayout.java      |   2 +-
 .../repositories/resolver/AbstractVersionList.java |  63 ++
 .../resolver/ChainedVersionLister.java             |  79 +++
 .../repositories/resolver/DefaultVersionList.java  |  49 ++
 .../resolver/ExternalResourceResolver.java         | 637 ++++++++++++++++++
 .../repositories/resolver/IvyResolver.java         |  45 ++
 .../repositories/resolver/IvyResourcePattern.java  |  63 ++
 .../repositories/resolver/M2ResourcePattern.java   |  63 ++
 .../repositories/resolver/MavenMetadata.java       |  26 +
 .../repositories/resolver/MavenMetadataLoader.java |  86 +++
 .../repositories/resolver/MavenPattern.java        |  23 +
 .../repositories/resolver/MavenResolver.java       | 257 ++++++++
 .../repositories/resolver/MavenVersionLister.java  |  49 ++
 .../resolver/PatternBasedResolver.java             |  33 +
 .../repositories/resolver/ResourcePattern.java     |  41 ++
 .../resolver/ResourceVersionLister.java            | 164 +++++
 .../repositories/resolver/VersionList.java         |  41 ++
 .../repositories/resolver/VersionLister.java       |  26 +
 .../transport/ProgressLoggingTransferListener.java |  58 ++
 .../transport/RepositoryTransport.java             |   2 +-
 .../transport/RepositoryTransportFactory.java      |  77 +--
 .../artifacts/result/DefaultResolutionResult.java  |  95 +++
 .../result/DefaultResolvedDependencyResult.java    |  58 ++
 .../result/DefaultResolvedModuleVersionResult.java |  81 +++
 .../result/DefaultUnresolvedDependencyResult.java  |  54 ++
 .../externalresource/AbstractExternalResource.java |  18 +-
 .../externalresource/ExternalResource.java         |   8 +-
 .../MetaDataOnlyExternalResource.java              |   6 -
 .../cached/CachedExternalResourceAdapter.java      |  13 +-
 .../cached/DefaultCachedExternalResourceIndex.java |   8 +-
 .../LocalMavenLocallyAvailableResourceFinder.java  |  84 ---
 .../ivy/LocallyAvailableResourceFinderFactory.java |  47 +-
 ...PatternBasedLocallyAvailableResourceFinder.java |  55 +-
 .../local/ivy/PatternTransformer.java              |  78 ---
 .../transfer/AbstractProgressLoggingHandler.java   |  41 ++
 .../CacheAwareExternalResourceAccessor.java        |   3 +-
 .../DefaultCacheAwareExternalResourceAccessor.java |   8 +-
 .../transfer/ExternalResourceUploader.java         |   8 +-
 .../ProgressLoggingExternalResourceAccessor.java   | 156 +++++
 .../ProgressLoggingExternalResourceUploader.java   |  81 +++
 .../transfer/ResourceOperation.java                |  70 ++
 .../DefaultExternalResourceRepository.java         | 125 ++++
 .../transport/ExternalResourceRepository.java      |  70 ++
 .../transport/file/FileResourceConnector.java      |  33 +-
 .../transport/file/FileTransport.java              |  28 +-
 .../http/CopyProgressListenerAdapter.java          |  31 -
 .../transport/http/HttpClientConfigurer.java       |  15 +-
 .../transport/http/HttpClientHelper.java           |  18 +-
 .../transport/http/HttpRequestException.java       |  31 +
 .../transport/http/HttpResourceAccessor.java       |  10 +-
 .../transport/http/HttpResourceLister.java         |  45 +-
 .../transport/http/HttpResourceUploader.java       |  10 +-
 .../transport/http/HttpTransport.java              |  31 +-
 .../http/RepeatableInputStreamEntity.java          |  60 ++
 .../internal/filestore/DefaultFileStoreEntry.java  |  39 --
 .../gradle/api/internal/filestore/FileStore.java   |  24 -
 .../filestore/GroupedAndNamedUniqueFileStore.java  |  58 --
 .../internal/filestore/UniquePathFileStore.java    |  98 ---
 .../filestore/ivy/ArtifactRevisionIdFileStore.java |   7 +-
 .../gradle/api/artifacts/ArtifactsTestUtils.java   |   2 +
 .../DefaultDependencyManagementServicesTest.groovy |   2 +
 .../artifacts/DefaultResolvedArtifactTest.groovy   |   7 +-
 ...cheLockingArtifactDependencyResolverTest.groovy |  12 +-
 ...efaultBuildableArtifactResolveResultTest.groovy |  86 +++
 ...tBuildableModuleVersionResolveResultTest.groovy | 113 ++++
 ...orHandlingArtifactDependencyResolverTest.groovy | 165 +++--
 .../ErrorHandlingArtifactPublisherTest.groovy      |  19 +-
 .../ivyservice/IvyBackedArtifactPublisherTest.java | 328 +++++-----
 .../IvyXmlModuleDescriptorWriterTest.groovy        | 152 +++++
 .../ivyservice/ResolvedArtifactFactoryTest.groovy  |   7 +-
 .../SelfResolvingDependencyResolverTest.groovy     | 131 ++++
 .../SelfResolvingDependencyResolverTest.java       | 177 -----
 ...ptyConfigsArtifactDependencyResolverSpec.groovy |  70 ++
 ...EmptyConfigsArtifactDependencyResolverTest.java |  79 ---
 ...ionForcingDependencyToModuleResolverTest.groovy |   5 +-
 .../clientmodule/ClientModuleResolverTest.groovy   |  49 +-
 .../CachingModuleVersionRepositoryTest.groovy      |  28 +-
 ...aultBuildableModuleVersionDescriptorTest.groovy | 121 ++++
 .../DependencyResolverIdentifierTest.groovy        |   2 +-
 .../LazyDependencyToModuleResolverTest.groovy      |  73 +--
 .../ivyresolve/UserResolverChainTest.groovy        | 513 +++++++++++++++
 .../modulecache/ModuleDescriptorStoreTest.groovy   |  76 +++
 .../ProjectDependencyResolverTest.groovy           |  23 +-
 .../DependencyGraphBuilderTest.groovy              |  97 ++-
 .../CachingDependencyResultFactoryTest.groovy      |  71 ++
 .../result/ResolutionResultBuilderSpec.groovy      | 264 ++++++++
 .../DefaultLocalMavenRepositoryLocatorTest.groovy  |   6 +-
 .../DefaultBaseRepositoryFactoryTest.groovy        | 155 +++++
 .../DefaultFlatDirArtifactRepositoryTest.groovy    |   6 +-
 .../DefaultIvyArtifactRepositoryTest.groovy        |  15 +-
 .../DefaultMavenArtifactRepositoryTest.groovy      |  11 +-
 .../repositories/DefaultResolverFactoryTest.groovy | 152 -----
 .../DownloadingRepositoryCacheManagerTest.groovy   |  71 ++
 .../resolver/ChainedVersionListerTest.groovy       | 125 ++++
 .../resolver/IvyResourcePatternTest.groovy         |  45 ++
 .../resolver/M2ResourcePatternTest.groovy          |  79 +++
 .../repositories/resolver/MavenResolverTest.groovy |  48 ++
 .../resolver/MavenVersionListerTest.groovy         | 174 +++++
 .../resolver/ResourceVersionListerTest.groovy      | 184 ++++++
 .../ProgressLoggingTransferListenerTest.groovy     |  78 +++
 .../result/DefaultResolutionResultTest.groovy      | 115 ++++
 .../DefaultResolvedModuleVersionResultSpec.groovy  |  67 ++
 .../CachedExternalResourceAdapterTest.groovy       |   8 +-
 ...ltCacheAwareExternalResourceAccessorTest.groovy |  18 +-
 ...gressLoggingExternalResourceAccessorTest.groovy | 124 ++++
 ...gressLoggingExternalResourceUploaderTest.groovy |  67 ++
 .../transfer/ResourceOperationTest.groovy          |  95 +++
 .../http/ApacheDirectoryListingParserTest.groovy   |  67 +-
 .../transport/http/HttpClientConfigurerTest.groovy |  15 +-
 .../transport/http/HttpClientHelperTest.groovy     |  46 ++
 .../transport/http/HttpResourceListerTest.groovy   |  10 +-
 .../result/ResolutionResultDataBuilder.groovy      |  42 ++
 subprojects/core/core.gradle                       |  11 +-
 .../api/dsl/DynamicObjectIntegrationTest.groovy    |  43 +-
 .../scripts/StatementLabelsIntegrationTest.groovy  |   7 +-
 .../groovy/org/gradle/BuildExceptionReporter.java  | 153 +++--
 .../src/main/groovy/org/gradle/StartParameter.java |  23 +
 .../groovy/org/gradle/TaskExecutionLogger.java     |  17 +-
 .../src/main/groovy/org/gradle/api/Project.java    |   2 +
 .../org/gradle/api/UncheckedIOException.java       |  39 --
 .../org/gradle/api/artifacts/Configuration.java    |   2 +
 .../api/artifacts/ConfigurationContainer.java      |   2 +
 .../gradle/api/artifacts/LenientConfiguration.java |   2 -
 .../api/artifacts/ModuleVersionSelector.java       |  13 +
 .../api/artifacts/ResolvableDependencies.java      |  11 +
 .../artifacts/cache/ArtifactResolutionControl.java |   4 +-
 .../cache/DependencyResolutionControl.java         |   4 +-
 .../artifacts/cache/ModuleResolutionControl.java   |   4 +-
 .../api/artifacts/cache/ResolutionControl.java     |   4 +-
 .../api/artifacts/cache/ResolutionRules.java       |   4 +-
 .../artifacts/repositories/ArtifactRepository.java |   8 +
 .../repositories/IvyArtifactRepository.java        |  26 +-
 .../api/artifacts/result/DependencyResult.java     |  42 ++
 .../result/ModuleVersionSelectionReason.java       |  41 ++
 .../api/artifacts/result/ResolutionResult.java     |  93 +++
 .../artifacts/result/ResolvedDependencyResult.java |  37 ++
 .../result/ResolvedModuleVersionResult.java        |  63 ++
 .../result/UnresolvedDependencyResult.java         |  25 +
 .../gradle/api/artifacts/result/package-info.java  |  20 +
 .../groovy/org/gradle/api/file/RelativePath.java   |   4 +-
 .../api/internal/AbstractClassGenerator.java       |  30 +-
 .../gradle/api/internal/AbstractDynamicObject.java |  10 +-
 .../api/internal/AsmBackedClassGenerator.java      | 166 ++++-
 .../org/gradle/api/internal/BeanDynamicObject.java |  28 +
 .../gradle/api/internal/ClosureBackedAction.java   |  68 ++
 .../api/internal/CompositeDomainObjectSet.java     |  16 +-
 .../api/internal/CompositeDynamicObject.java       |  60 ++
 .../org/gradle/api/internal/ConfigureDelegate.java |  11 +-
 .../internal/DefaultDomainObjectCollection.java    |   7 +-
 .../internal/DependencyInjectingInstantiator.java  | 185 ++++++
 .../gradle/api/internal/DocumentationRegistry.java |  14 +-
 .../org/gradle/api/internal/DynamicObject.java     |   8 +
 .../gradle/api/internal/DynamicObjectHelper.java   |   8 +
 .../api/internal/DynamicPropertyNamer.groovy       |  13 -
 .../api/internal/ExtensibleDynamicObject.java      |   8 +
 .../org/gradle/api/internal/FilteredAction.java    |  38 --
 .../api/internal/NoNamingPropertyException.java    |  23 +
 .../api/internal/NullNamingPropertyException.java  |  23 +
 .../org/gradle/api/internal/XmlTransformer.java    |  39 +-
 .../artifacts/ArtifactDependencyResolver.java      |   3 +-
 .../api/internal/artifacts/ArtifactPublisher.java  |   8 +-
 .../artifacts/ArtifactPublisherFactory.java        |  25 +
 .../internal/artifacts/BaseRepositoryFactory.java  |  49 ++
 .../artifacts/DefaultArtifactPublisherFactory.java |  46 ++
 .../DefaultArtifactRepositoryContainer.java        |  57 +-
 .../api/internal/artifacts/DefaultExcludeRule.java |   2 +-
 .../artifacts/DefaultModuleVersionIdentifier.java  |  12 +-
 .../artifacts/DefaultModuleVersionSelector.java    |  11 +-
 .../artifacts/DependencyResolutionServices.java    |   2 +-
 .../artifacts/ModuleVersionSelectorStrictSpec.java |  40 ++
 .../artifacts/ResolvedConfigurationIdentifier.java |  32 +-
 .../api/internal/artifacts/ResolverFactory.java    |  38 --
 .../api/internal/artifacts/ResolverResults.java    |  62 ++
 .../configurations/ConfigurationInternal.java      |   2 +-
 .../artifacts/configurations/Configurations.java   |   4 +-
 .../configurations/DefaultConfiguration.java       |  26 +-
 .../configurations/dynamicversion/CachePolicy.java |   3 +-
 .../dynamicversion/DefaultCachePolicy.java         |  23 +-
 .../dependencies/AbstractExternalDependency.java   |   6 +
 .../artifacts/dsl/DefaultRepositoryFactory.java    |  98 +++
 .../artifacts/dsl/DefaultRepositoryHandler.java    |  77 ++-
 .../internal/artifacts/dsl/RepositoryFactory.java  | 111 ++++
 .../artifacts/dsl/RepositoryFactoryInternal.java   |  25 +
 .../dependencies/ModuleDescriptorDelegate.groovy   |   1 -
 .../repositories/AbstractArtifactRepository.java   |  42 ++
 .../repositories/ArtifactRepositoryInternal.java   |   5 +
 .../FixedResolverArtifactRepository.java           |   7 +-
 .../result/ResolvedDependencyResultPrinter.java    |  39 ++
 .../version/LatestVersionSemanticComparator.java   |  49 ++
 .../api/internal/classpath/ManifestUtil.java       |   4 +-
 .../collections/CollectionEventRegister.java       |  13 +-
 .../api/internal/file/AbstractFileCollection.java  |   2 +-
 .../api/internal/file/AbstractFileResolver.java    |   6 +-
 .../api/internal/file/AbstractFileTreeElement.java |   9 +-
 .../api/internal/file/BaseDirFileResolver.java     |   3 +-
 .../file/DefaultTemporaryFileProvider.java         |  12 +-
 .../api/internal/file/FileOrUriNotationParser.java |   6 +-
 .../internal/file/TmpDirTemporaryFileProvider.java |  24 -
 .../api/internal/file/archive/TarFileTree.java     |   5 +-
 .../api/internal/file/archive/ZipFileTree.java     |   5 +-
 .../api/internal/file/copy/PathNotationParser.java |   8 +-
 .../internal/filestore/AbstractFileStoreEntry.java |  28 +
 .../gradle/api/internal/filestore/FileStore.java   |  31 +
 .../api/internal/filestore/FileStoreEntry.java     |   0
 .../api/internal/filestore/FileStoreSearcher.java  |   0
 .../filestore/GroupedAndNamedUniqueFileStore.java  |  80 +++
 .../api/internal/filestore/PathKeyFileStore.java   | 201 ++++++
 .../filestore/PathNormalisingKeyFileStore.java     |  63 ++
 .../internal/filestore/UniquePathKeyFileStore.java |  51 ++
 .../internal/notations/NotationParserBuilder.java  |  10 +-
 .../gradle/api/internal/notations/TypeInfo.java    |  35 +
 .../api/internal/notations/api/NotationParser.java |   4 +-
 .../parsers/ClosureToSpecNotationParser.java       |  41 ++
 .../api/internal/plugins/DefaultConvention.java    |   8 +
 .../plugins/DefaultExtraPropertiesExtension.java   |   2 +-
 .../internal/plugins/DefaultPluginRegistry.java    |  25 +-
 .../api/internal/plugins/PluginRegistry.java       |   3 +-
 .../api/internal/project/AbstractProject.java      |   4 +-
 .../internal/project/GlobalServicesRegistry.java   |   8 +-
 .../project/GradleInternalServiceRegistry.java     |   9 +-
 .../project/ProjectInternalServiceRegistry.java    |  12 +-
 .../internal/project/TaskExecutionServices.java    |  13 +-
 .../project/TopLevelBuildServiceRegistry.java      |  26 +-
 .../AnnotationProcessingTaskFactory.java           |  17 +-
 .../taskfactory/DependencyAutoWireTaskFactory.java |  21 +-
 .../internal/project/taskfactory/ITaskFactory.java |   5 +-
 .../internal/project/taskfactory/TaskFactory.java  |  43 +-
 .../resource/ResourceNotFoundException.java        |   4 +
 .../gradle/api/internal/resource/UriResource.java  |  34 +-
 .../api/internal/tasks/DefaultTaskContainer.java   |  11 +-
 .../groovy/org/gradle/api/invocation/Gradle.java   |   2 +
 .../org/gradle/api/plugins/ExtensionAware.java     |   2 +-
 .../ivy/internal/IvyNormalizedPublication.java     |  55 ++
 .../api/publish/ivy/internal/IvyPublisher.java     |  33 +
 .../main/groovy/org/gradle/api/specs/AndSpec.java  |  55 --
 .../groovy/org/gradle/api/specs/CompositeSpec.java |  66 --
 .../main/groovy/org/gradle/api/specs/NotSpec.java  |  34 -
 .../main/groovy/org/gradle/api/specs/OrSpec.java   |  45 --
 .../main/groovy/org/gradle/api/specs/Specs.java    |  10 +-
 .../groovy/org/gradle/api/specs/package-info.java  |   2 +-
 .../groovy/org/gradle/api/tasks/Directory.groovy   |   2 +-
 .../src/main/groovy/org/gradle/api/tasks/Exec.java |  10 +-
 .../groovy/org/gradle/api/tasks/TaskContainer.java |   2 +-
 .../main/groovy/org/gradle/api/tasks/Upload.java   |   8 +-
 .../tasks/diagnostics/DependencyReportTask.java    |  90 ---
 .../api/tasks/diagnostics/PropertyReportTask.java  |  50 --
 .../AggregateMultiProjectTaskReportModel.java      |  87 ---
 .../diagnostics/internal/AsciiReportRenderer.java  | 172 -----
 .../internal/DefaultGroupTaskReportModel.java      |  82 ---
 .../internal/DependencyReportRenderer.java         |  47 --
 .../tasks/diagnostics/internal/GraphRenderer.java  |  65 --
 .../internal/GraphvizReportRenderer.java           |  75 ---
 .../diagnostics/internal/TaskReportRenderer.java   | 156 -----
 .../gradle/api/tasks/diagnostics/package-info.java |  20 -
 .../org/gradle/api/tasks/util/PatternSet.groovy    | 248 -------
 .../org/gradle/api/tasks/util/PatternSet.java      | 250 +++++++
 .../internal/PatternSetAntBuilderDelegate.java     | 102 +++
 .../gradle/cache/internal/DefaultCacheAccess.java  | 186 ++++--
 .../cache/internal/DefaultFileLockManager.java     |  10 +-
 .../internal/DefaultPersistentDirectoryStore.java  |   3 +-
 .../DelegateOnDemandPersistentDirectoryCache.java  |   4 +-
 .../cache/internal/UnitOfWorkParticipant.java      |   9 +
 .../configuration/DefaultBuildConfigurer.java      |  17 +-
 .../main/groovy/org/gradle/configuration/Help.java |  48 --
 .../configuration/ImplicitTasksConfigurer.java     |  33 +-
 .../gradle/execution/DefaultTaskGraphExecuter.java | 241 -------
 ...ludedTaskFilteringBuildConfigurationAction.java |  17 +-
 .../gradle/execution/MultipleBuildFailures.java    |  31 +
 .../execution/SelectedTaskExecutionAction.java     |  31 +-
 .../TaskNameResolvingBuildConfigurationAction.java |  49 +-
 .../groovy/org/gradle/execution/TaskSelector.java  |  46 +-
 .../commandline/CommandLineTaskConfigurer.java     | 108 ++++
 .../commandline/CommandLineTaskParser.java         |  63 ++
 .../taskgraph/DefaultTaskExecutionPlan.java        | 248 +++++++
 .../taskgraph/DefaultTaskGraphExecuter.java        | 145 +++++
 .../taskgraph/DefaultTaskPlanExecutor.java         |  57 ++
 .../execution/taskgraph/ExecutionOptions.java      |  36 ++
 .../taskgraph/ParallelTaskPlanExecutor.java        | 131 ++++
 .../execution/taskgraph/TaskExecutionPlan.java     |  52 ++
 .../org/gradle/execution/taskgraph/TaskInfo.java   | 109 ++++
 .../execution/taskgraph/TaskPlanExecutor.java      |  23 +
 .../taskgraph/TaskPlanExecutorFactory.java         |  52 ++
 .../org/gradle/groovy/scripts/DefaultScript.java   |   2 +-
 .../internal/StatementLabelsDeprecationLogger.java |  11 +-
 .../org/gradle/initialization/BaseSettings.java    |  14 -
 .../initialization/DefaultClassLoaderRegistry.java |   1 +
 .../DefaultCommandLineConverter.java               |  20 +-
 .../initialization/DefaultGradleLauncher.java      |   6 +-
 .../DefaultGradleLauncherFactory.java              |   2 +-
 .../gradle/initialization/DefaultSettings.groovy   |   7 -
 .../initialization/DependencyResolutionLogger.java |  22 +-
 .../initialization/ModelConfigurationListener.java |   2 +-
 .../MultipleBuildFailuresExceptionAnalyser.java    |  49 ++
 .../org/gradle/initialization/SettingsFactory.java |  18 +-
 .../initialization/TasksCompletionListener.java    |  26 +
 .../org/gradle/logging/LoggingServiceRegistry.java | 169 +++--
 .../org/gradle/logging/internal/AnsiConsole.java   |   3 -
 .../internal/ConsoleBackedProgressRenderer.java    |  20 +-
 .../logging/internal/ConsoleConfigureAction.java   |  49 ++
 .../logging/internal/DefaultLoggingConfigurer.java |   4 -
 .../logging/internal/DefaultLoggingManager.java    |  10 +-
 .../internal/DefaultProgressLoggerFactory.java     |   2 +-
 .../internal/DefaultStandardOutputRedirector.java  |   4 +-
 .../internal/DefaultStatusBarFormatter.java        |  54 ++
 .../internal/EmbeddedLoggingManagerFactory.java    |  36 --
 .../internal/LoggingBackedStyledTextOutput.java    |   2 +-
 .../logging/internal/LoggingOutputInternal.java    |  10 +-
 .../logging/internal/OutputEventRenderer.java      |  83 +--
 .../logging/internal/PrintStreamLoggingSystem.java |   2 +-
 .../logging/internal/ProgressCompleteEvent.java    |   8 +-
 .../logging/internal/StatusBarFormatter.java       |  23 +
 .../logging/internal/TerminalDetectorFactory.java  |  44 --
 .../logback/SimpleLogbackLoggingConfigurer.java    |  42 --
 .../process/internal/DefaultWorkerProcess.java     |  11 +-
 .../process/internal/JavaExecHandleBuilder.java    |   5 +-
 .../org/gradle/process/internal/JvmOptions.java    |  75 +--
 .../internal/ProcessParentingInitializer.java      |   3 +-
 .../process/internal/WorkerProcessBuilder.java     |  14 +-
 .../internal/child/ActionExecutionWorker.java      |   4 +-
 .../child/ImplementationClassLoaderWorker.java     |   2 +-
 .../internal/streams/ExecOutputHandleRunner.java   |   2 +-
 .../org/gradle/reporting/TextReportRenderer.java   |  21 +-
 .../testfixtures/internal/GlobalTestServices.java  |   2 +-
 .../testfixtures/internal/NoOpLoggingManager.java  |   5 +-
 .../internal/TestTopLevelBuildServiceRegistry.java |   2 +-
 .../org/gradle/util/AvailablePortFinder.java       |   5 +-
 .../src/main/groovy/org/gradle/util/Clock.java     |  22 +-
 .../groovy/org/gradle/util/CollectionUtils.java    | 107 ---
 .../main/groovy/org/gradle/util/ConfigureUtil.java |  20 +-
 .../groovy/org/gradle/util/DeprecationLogger.java  |  92 ++-
 .../org/gradle/util/DisconnectableInputStream.java |  68 +-
 .../main/groovy/org/gradle/util/GFileUtils.java    | 109 +++-
 .../src/main/groovy/org/gradle/util/GUtil.java     |  23 +-
 .../main/groovy/org/gradle/util/GradleVersion.java |  25 +
 .../core/src/main/groovy/org/gradle/util/Jvm.java  |   2 +-
 .../org/gradle/util/LineBufferingOutputStream.java |  44 +-
 .../util/LinePerThreadBufferingOutputStream.java   |   8 +-
 .../src/main/groovy/org/gradle/util/TextUtil.java  |   8 +
 .../org/gradle/util/ToStringTransformer.java       |  27 -
 .../main/groovy/org/gradle/util/VersionNumber.java | 102 +++
 .../org/gradle/configuration/default-imports.txt   |   6 +
 .../org/gradle/BuildExceptionReporterTest.groovy   |  49 ++
 .../org/gradle/TaskExecutionLoggerTest.groovy      | 108 ++++
 .../groovy/org/gradle/TaskExecutionLoggerTest.java | 128 ----
 .../AsmBackedClassGeneratorGroovyTest.groovy       | 215 ++++++
 .../api/internal/AsmBackedClassGeneratorTest.java  | 196 +++++-
 .../org/gradle/api/internal/DefaultTaskTest.groovy |  13 +-
 .../DependencyInjectingInstantiatorTest.groovy     | 304 +++++++++
 .../api/internal/DocumentationRegistryTest.groovy  |   9 +-
 .../api/internal/ExtensibleDynamicObjectTest.java  | 124 ++--
 .../ExtensibleDynamicObjectTestHelper.groovy       |  29 +-
 .../FactoryNamedDomainObjectContainerSpec.groovy   |   3 +-
 .../gradle/api/internal/FilteredActionSpec.groovy  |  69 --
 ...AutoCreateNamedDomainObjectContainerSpec.groovy |   2 +-
 .../gradle/api/internal/XmlTransformerTest.groovy  |  29 +
 .../DefaultArtifactRepositoryContainerTest.groovy  | 378 ++++++-----
 .../ModuleVersionSelectorStrictSpecTest.groovy     |  44 ++
 .../internal/artifacts/ResolverResultsSpec.groovy  |  55 ++
 .../configurations/DefaultConfigurationSpec.groovy |   4 +-
 .../configurations/DefaultConfigurationTest.java   |   8 +-
 .../dynamicversion/DefaultCachePolicySpec.groovy   |  20 +-
 .../dsl/DefaultRepositoryFactoryTest.groovy        | 137 ++++
 .../dsl/DefaultRepositoryHandlerTest.groovy        | 427 ++++--------
 .../dsl/TestFlatDirectoryArtifactRepository.java   |  23 +
 .../artifacts/dsl/TestIvyArtifactRepository.java   |  23 +
 .../artifacts/dsl/TestMavenArtifactRepository.java |  23 +
 ...meAfterContainerInclusionDeprecationTest.groovy |  70 ++
 .../LatestVersionSemanticComparatorSpec.groovy     |  76 +++
 .../internal/file/AbstractFileTreeElementTest.java |  47 +-
 .../internal/file/DefaultFileOperationsTest.groovy |   3 +-
 .../internal/file/copy/DeleteActionImplTest.groovy |  12 +-
 .../internal/filestore/PathKeyFileStoreTest.groovy | 198 ++++++
 .../PathNormalisingKeyFileStoreTest.groovy         |  94 +++
 .../filestore/UniquePathKeyFileStoreTest.groovy    | 112 ++++
 .../parsers/ClosureToSpecNotationParserSpec.groovy |  40 ++
 .../plugins/DefaultPluginRegistryTest.groovy       | 218 +++++++
 .../plugins/DefaultPluginRegistryTest.java         | 256 --------
 .../project/DefaultIsolatedAntBuilderTest.groovy   |  38 +-
 .../project/GlobalServicesRegistryTest.java        |   5 +
 .../GradleInternalServiceRegistryTest.groovy       | 101 +++
 .../project/GradleInternalServiceRegistryTest.java | 108 ----
 .../ProjectInternalServiceRegistryTest.java        |  20 +-
 .../TopLevelBuildServiceRegistryTest.groovy        |  18 +-
 .../AnnotationProcessingTaskFactoryTest.java       |   6 +-
 .../DependencyAutoWireTaskFactoryTest.java         |  13 +-
 .../project/taskfactory/TaskFactoryTest.groovy     | 167 +++++
 .../project/taskfactory/TaskFactoryTest.java       | 233 -------
 .../internal/tasks/DefaultTaskContainerTest.java   |  20 +-
 .../tasks/util/DefaultJavaForkOptionsTest.groovy   |   4 +-
 .../groovy/org/gradle/api/logging/LoggingTest.java |  10 +-
 .../api/specs/AbstractCompositeSpecTest.java       |  74 ---
 .../groovy/org/gradle/api/specs/AndSpecTest.java   |  60 --
 .../groovy/org/gradle/api/specs/NotSpecTest.java   |  39 --
 .../groovy/org/gradle/api/specs/OrSpecTest.java    |  48 --
 .../groovy/org/gradle/api/specs/SpecsTest.groovy   |   2 +-
 .../org/gradle/api/tasks/AbstractCopyTaskTest.java |   1 -
 .../groovy/org/gradle/api/tasks/CopyTest.groovy    |   1 -
 .../groovy/org/gradle/api/tasks/DeleteTest.java    |   1 -
 .../org/gradle/api/tasks/DirectoryTest.groovy      |   1 -
 .../groovy/org/gradle/api/tasks/ExecTest.groovy    |   1 -
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |   1 -
 .../org/gradle/api/tasks/SourceTaskTest.groovy     |   1 -
 .../groovy/org/gradle/api/tasks/UploadTest.java    |  15 +-
 .../org/gradle/api/tasks/bundling/TarTest.groovy   |   1 -
 .../org/gradle/api/tasks/bundling/ZipTest.groovy   |   1 -
 .../diagnostics/DependencyReportTaskTest.java      | 140 ----
 ...AggregateMultiProjectTaskReportModelTest.groovy | 115 ----
 .../internal/AsciiReportRendererTest.groovy        | 116 ----
 .../DefaultGroupTaskReportModelTest.groovy         | 110 ----
 .../SingleProjectTaskReportModelTest.groovy        | 179 -----
 .../internal/TaskDetailsFactoryTest.groovy         |  74 ---
 .../internal/TaskModelSpecification.groovy         |  49 --
 .../internal/TaskReportRendererTest.groovy         | 184 ------
 .../internal/TextReportRendererTest.groovy         | 115 ----
 .../gradle/api/tasks/util/PatternSetTest.groovy    |  20 +-
 .../cache/internal/DefaultCacheAccessTest.groovy   | 213 ++++--
 .../cache/internal/DefaultCacheFactoryTest.groovy  |   2 +-
 .../cache/internal/OnDemandFileAccessTest.groovy   |   4 +-
 .../ImplicitTasksConfigurerTest.groovy             |  13 +-
 .../execution/DefaultTaskGraphExecuterTest.java    | 579 -----------------
 ...askFilteringBuildConfigurationActionTest.groovy |   8 +-
 .../SelectedTaskExecutionActionTest.groovy         |  34 +-
 .../CommandLineTaskConfigurerSpec.groovy           | 177 +++++
 .../commandline/CommandLineTaskParserSpec.groovy   | 119 ++++
 .../taskgraph/DefaultTaskExecutionPlanTest.groovy  | 442 +++++++++++++
 .../taskgraph/DefaultTaskGraphExecuterTest.java    | 578 +++++++++++++++++
 .../taskgraph/TaskPlanExecutorFactoryTest.groovy   |  46 ++
 .../DefaultCommandLineConverterTest.java           |  21 +
 .../DefaultExceptionAnalyserTest.java              |   9 +-
 .../initialization/DefaultGradleLauncherTest.java  |   6 +-
 .../initialization/DefaultSettingsTest.groovy      |   5 +-
 .../gradle/initialization/SettingsFactoryTest.java |   6 +-
 .../org/gradle/logging/ConfigureLogging.groovy     |  64 ++
 .../logging/LoggingServiceRegistryTest.groovy      | 211 +++++-
 .../org/gradle/logging/LoggingTestHelper.groovy    |  53 --
 .../groovy/org/gradle/logging/TestAppender.groovy  |  40 ++
 .../ConsoleBackedProgressRendererTest.groovy       |   5 +-
 .../internal/DefaultStatusBarFormatterTest.groovy  |  44 ++
 .../internal/JavaUtilLoggingConfigurerTest.groovy  |  27 +-
 .../internal/OutputEventRendererTest.groovy        |  96 ++-
 .../logging/internal/OutputSpecification.groovy    |   2 +-
 .../internal/TerminalDetectorFactoryTest.groovy    |  63 --
 .../process/internal/DefaultExecHandleSpec.groovy  |  33 +-
 .../internal/DefaultWorkerProcessFactoryTest.java  |   2 +-
 .../internal/DefaultWorkerProcessTest.groovy       |   4 +-
 .../gradle/process/internal/JvmOptionsTest.groovy  |  87 ++-
 .../org/gradle/util/CollectionUtilsTest.groovy     |  91 ---
 .../org/gradle/util/DeprecationLoggerTest.groovy   |  83 +++
 .../util/DisconnectableInputStreamTest.groovy      |  64 +-
 .../groovy/org/gradle/util/GFileUtilsTest.groovy   |  70 +-
 .../test/groovy/org/gradle/util/GUtilTest.groovy   |   6 +
 .../org/gradle/util/GradleVersionTest.groovy       |  33 +
 .../gradle/util/LineBufferingOutputStreamTest.java |  41 +-
 .../LinePerThreadBufferingOutputStreamTest.groovy  |   4 +-
 .../org/gradle/util/VersionNumberTest.groovy       |  96 +++
 .../org/gradle/api/internal/file/TestFiles.java    |  36 ++
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |  24 +-
 .../org/gradle/api/tasks/AbstractTaskTest.java     |  63 +-
 .../DefaultFileLockManagerTestHelper.groovy        |  13 +-
 .../groovy/org/gradle/util/HelperUtil.groovy       |  12 +-
 .../groovy/org/gradle/util/Matchers.java           |  13 +
 .../groovy/org/gradle/util/MockExecutor.java       |  35 +
 .../org/gradle/util/MultithreadedTestCase.java     |   4 +-
 subprojects/cpp/cpp.gradle                         |   7 +
 .../plugins/cpp/CppIntegrationTestRunner.java      |   2 +-
 .../plugins/cpp/CppSamplesIntegrationTest.groovy   |  18 +-
 .../gradle/plugins/binaries/BinariesPlugin.java    |  12 +-
 .../binaries/model/internal/CompileTaskAware.java  |   4 +-
 .../gradle/plugins/binaries/tasks/Compile.groovy   |  32 -
 .../org/gradle/plugins/cpp/CppCompile.groovy       |  33 +
 .../groovy/org/gradle/plugins/cpp/CppPlugin.groovy |   3 +-
 .../gradle/plugins/cpp/gpp/GppCompileSpec.groovy   |  26 +-
 .../plugins/cpp/internal/DefaultCppSourceSet.java  |   2 -
 .../cpp/msvcpp/MicrosoftVisualCppPlugin.groovy     |   5 +
 .../org/gradle/plugins/cpp/CppPluginTest.groovy    |  18 +-
 .../plugins/cpp/gpp/GppCompileSpecTest.groovy      |   4 +-
 subprojects/diagnostics/diagnostics.gradle         |  25 +
 .../ProjectReportsPluginIntegrationTest.java       |  33 +
 ...pendencyInsightReportTaskIntegrationTest.groovy | 488 ++++++++++++++
 .../DependencyReportTaskIntegrationTest.groovy     | 420 ++++++++++++
 .../org/gradle/api/plugins/HelpTasksPlugin.groovy  |  67 ++
 .../gradle/api/plugins/ProjectReportsPlugin.java   |   0
 .../plugins/ProjectReportsPluginConvention.groovy  |  43 ++
 .../api/tasks/diagnostics/AbstractReportTask.java  |   0
 .../diagnostics/DependencyInsightReportTask.groovy | 201 ++++++
 .../tasks/diagnostics/DependencyReportTask.java    |  90 +++
 .../api/tasks/diagnostics/ProjectReportTask.java   |   0
 .../api/tasks/diagnostics/PropertyReportTask.java  |  50 ++
 .../api/tasks/diagnostics/ReportException.java     |  29 +
 .../api/tasks/diagnostics/TaskReportTask.java      |   0
 .../AggregateMultiProjectTaskReportModel.java      |  90 +++
 .../internal/DefaultGroupTaskReportModel.java      |  82 +++
 .../internal/DependencyReportRenderer.java         |  46 ++
 .../tasks/diagnostics/internal/GraphRenderer.java  |  69 ++
 .../internal/PropertyReportRenderer.java           |   0
 .../tasks/diagnostics/internal/ReportRenderer.java |   0
 .../internal/SingleProjectTaskReportModel.java     |   0
 .../tasks/diagnostics/internal/TaskDetails.java    |   0
 .../diagnostics/internal/TaskDetailsFactory.java   |   0
 .../diagnostics/internal/TaskReportModel.java      |   0
 .../diagnostics/internal/TaskReportRenderer.java   | 157 +++++
 .../diagnostics/internal/TextReportRenderer.java   |   0
 .../AsciiDependencyReportRenderer.java             | 112 ++++
 .../internal/dsl/DependencyResultSpec.java         |  54 ++
 .../dsl/DependencyResultSpecNotationParser.java    |  57 ++
 .../internal/graph/DependencyGraphRenderer.groovy  |  81 +++
 .../diagnostics/internal/graph/NodeRenderer.groovy |  28 +
 .../internal/graph/SimpleNodeRenderer.java         |  36 ++
 .../nodes/AbstractRenderableDependencyResult.java  |  82 +++
 .../nodes/AbstractRenderableModuleResult.java      |  53 ++
 .../nodes/InvertedRenderableDependencyResult.java  |  46 ++
 .../nodes/InvertedRenderableModuleResult.java      |  45 ++
 .../internal/graph/nodes/RenderableDependency.java |  31 +
 .../graph/nodes/RenderableDependencyResult.java    |  48 ++
 .../graph/nodes/RenderableModuleResult.java        |  45 ++
 .../internal/graph/nodes/SimpleDependency.java     |  58 ++
 .../insight/DependencyInsightReporter.groovy       |  69 ++
 .../insight/ResolvedDependencyResultSorter.java    |  76 +++
 .../gradle/api/tasks/diagnostics/package-info.java |  22 +
 .../main/groovy/org/gradle/configuration/Help.java |  48 ++
 .../META-INF/gradle-plugins/help-tasks.properties  |   1 +
 .../gradle-plugins/project-report.properties       |   0
 .../gradle-plugins/project-reports.properties      |   0
 .../gradle/api/plugins/HelpTasksPluginSpec.groovy  |  68 ++
 .../api/plugins/ProjectReportsPluginTest.java      |   0
 .../ReportingBasePluginConventionTest.groovy       |   0
 .../api/plugins/ReportingBasePluginTest.groovy     |  46 ++
 .../tasks/diagnostics/AbstractReportTaskTest.java  |   0
 .../DependencyInsightReportTaskSpec.groovy         |  78 +++
 .../diagnostics/DependencyReportTaskTest.groovy    |  79 +++
 .../tasks/diagnostics/ProjectReportTaskTest.groovy |   0
 .../tasks/diagnostics/PropertyReportTaskTest.java  |   0
 .../api/tasks/diagnostics/TaskReportTaskTest.java  |   0
 .../internal/AbstractTaskModelSpec.groovy          |  49 ++
 ...AggregateMultiProjectTaskReportModelTest.groovy | 115 ++++
 .../DefaultGroupTaskReportModelTest.groovy         | 110 ++++
 .../internal/PropertyReportRendererTest.java       |   0
 .../SingleProjectTaskReportModelTest.groovy        | 179 +++++
 .../internal/TaskDetailsFactoryTest.groovy         |  74 +++
 .../internal/TaskReportRendererTest.groovy         | 184 ++++++
 .../internal/TextReportRendererTest.groovy         | 116 ++++
 .../AsciiDependencyReportRendererTest.groovy       | 105 +++
 .../DependencyResultSpecNotationParserSpec.groovy  |  97 +++
 .../internal/dsl/DependencyResultSpecTest.groovy   |  69 ++
 .../graph/DependencyGraphRendererSpec.groovy       |  85 +++
 .../AbstractRenderableDependencyResultSpec.groovy  |  46 ++
 .../InvertedRenderableDependencyResultTest.groovy  |  72 +++
 .../insight/DependencyInsightReporterSpec.groovy   | 108 ++++
 .../ResolvedDependencyResultSorterSpec.groovy      |  79 +++
 subprojects/distributions/distributions.gradle     | 134 ++++
 .../gradle/AllDistributionIntegrationSpec.groovy   |  72 +++
 .../gradle/BinDistributionIntegrationSpec.groovy   |  34 +
 .../org/gradle/DistributionIntegrationSpec.groovy  |  93 +++
 .../gradle/SrcDistributionIntegrationSpec.groovy   |  58 ++
 .../distributions/src}/toplevel/LICENSE            |   0
 .../distributions/src}/toplevel/NOTICE             |   0
 .../distributions/src}/toplevel/changelog.txt      |   0
 .../distributions/src}/toplevel/init.d/readme.txt  |   0
 .../src}/toplevel/media/gradle-icon-128x128.png    | Bin
 .../src}/toplevel/media/gradle-icon-16x16.png      | Bin
 .../src}/toplevel/media/gradle-icon-24x24.png      | Bin
 .../src}/toplevel/media/gradle-icon-256x256.png    | Bin
 .../src}/toplevel/media/gradle-icon-32x32.png      | Bin
 .../src}/toplevel/media/gradle-icon-48x48.png      | Bin
 .../src}/toplevel/media/gradle-icon-512x512.png    | Bin
 .../src}/toplevel/media/gradle-icon-64x64.png      | Bin
 .../distributions/src}/toplevel/media/gradle.icns  | Bin
 subprojects/docs/docs.gradle                       | 146 +++--
 subprojects/docs/release-notes-transform.gradle    |  59 +-
 subprojects/docs/src/docs/css/base.css             | 369 ++++++-----
 subprojects/docs/src/docs/css/docs.css             | 134 ++++
 subprojects/docs/src/docs/css/dsl.css              |  53 +-
 .../docs/src/docs/css/images/gradle-logo_25o.gif   | Bin 0 -> 2241 bytes
 subprojects/docs/src/docs/css/release-notes.css    |  72 +++
 subprojects/docs/src/docs/css/style.css            |  58 --
 subprojects/docs/src/docs/css/userguide.css        |  69 +-
 subprojects/docs/src/docs/dsl/dsl.xml              |  89 ++-
 .../dsl/org.gradle.api.artifacts.Configuration.xml |   6 +
 ....buildcomparison.gradle.CompareGradleBuilds.xml |  45 ++
 ...comparison.gradle.GradleBuildInvocationSpec.xml |  39 ++
 .../org.gradle.api.plugins.quality.FindBugs.xml    |  24 +
 ...radle.api.plugins.quality.FindBugsExtension.xml |  26 +-
 ...org.gradle.api.publish.PublicationContainer.xml |  22 +
 .../org.gradle.api.publish.PublishingExtension.xml |  34 +
 ....gradle.api.publish.ivy.IvyModuleDescriptor.xml |  30 +
 .../org.gradle.api.publish.ivy.IvyPublication.xml  |  28 +
 ...rg.gradle.api.tasks.compile.AbstractOptions.xml |  22 +
 ...org.gradle.api.tasks.compile.CompileOptions.xml | 108 ++++
 ...adle.api.tasks.compile.GroovyCompileOptions.xml |  78 +++
 .../org.gradle.api.tasks.compile.JavaCompile.xml   |  31 +
 ...sks.diagnostics.DependencyInsightReportTask.xml |  47 ++
 .../org.gradle.api.tasks.scala.ScalaCompile.xml    |   4 +
 ....gradle.api.tasks.scala.ScalaCompileOptions.xml |  95 +++
 .../docs/dsl/org.gradle.api.tasks.testing.Test.xml |   5 +-
 ...radle.api.tasks.testing.logging.TestLogging.xml |  68 ++
 ....tasks.testing.logging.TestLoggingContainer.xml |  33 +-
 subprojects/docs/src/docs/dsl/plugins.xml          |   5 +-
 .../docs/src/docs/release/content/Lato-bold.woff   | Bin 37284 -> 0 bytes
 .../src/docs/release/content/Lato-regular.woff     | Bin 35884 -> 0 bytes
 .../src/docs/release/content/jquery-1.7.2-min.js   |   4 -
 subprojects/docs/src/docs/release/content/logo.gif | Bin 7488 -> 0 bytes
 .../docs/src/docs/release/content/style.css        | 177 -----
 subprojects/docs/src/docs/release/notes.md         | 424 ++++++++----
 subprojects/docs/src/docs/stylesheets/dslHtml.xsl  |   2 +-
 .../docs/src/docs/stylesheets/standaloneHtml.xsl   |   2 +-
 .../src/docs/stylesheets/userGuideHtmlCommon.xsl   |   2 +-
 .../docs/src/docs/stylesheets/userGuidePdf.xsl     |   2 +-
 .../docs/src/docs/userguide/artifactMngmt.xml      |   8 +-
 .../docs/src/docs/userguide/bootstrapPlugin.xml    |  88 +++
 .../docs/userguide/buildAnnouncementsPlugin.xml    |   2 +-
 .../docs/src/docs/userguide/buildEnvironment.xml   |   2 +-
 .../docs/src/docs/userguide/commandLine.xml        |  22 +
 .../src/docs/userguide/commandLineTutorial.xml     |  37 ++
 .../docs/src/docs/userguide/comparingBuilds.xml    | 233 +++++++
 subprojects/docs/src/docs/userguide/depMngmt.xml   | 185 +++---
 .../docs/src/docs/userguide/eclipsePlugin.xml      |   2 +-
 subprojects/docs/src/docs/userguide/embedding.xml  |   6 +-
 .../docs/src/docs/userguide/featureLifecycle.xml   | 132 ++++
 .../docs/src/docs/userguide/gradleDaemon.xml       |  14 +-
 subprojects/docs/src/docs/userguide/javaPlugin.xml |   8 +-
 subprojects/docs/src/docs/userguide/logging.xml    |  13 +-
 .../docs/src/docs/userguide/multiproject.xml       |  23 +
 subprojects/docs/src/docs/userguide/plugins.xml    |   2 +-
 .../docs/src/docs/userguide/projectReports.xml     |   9 +-
 .../docs/src/docs/userguide/publishingIvy.xml      | 228 +++++++
 .../docs/src/docs/userguide/scalaPlugin.xml        |  77 ++-
 .../docs/src/docs/userguide/standardPlugins.xml    |  15 +-
 .../docs/src/docs/userguide/troubleshooting.xml    |   1 -
 subprojects/docs/src/docs/userguide/userguide.xml  |  13 +-
 .../docs/src/docs/userguide/workingWithFiles.xml   |   2 +-
 .../multiproject/groovycDetector/build.gradle      |   2 +-
 .../groovy/multiproject/testproject/build.gradle   |   2 +-
 .../src/test/groovy/org/gradle/VersionTest.groovy  |   4 +-
 .../docs/src/samples/ivypublish-new/build.gradle   |  60 ++
 .../docs/src/samples/ivypublish-new/output-ivy.xml |  23 +
 .../src/samples/ivypublish-new/settings.gradle     |  18 +
 .../src/main/java/org/gradle/SomeClass.java        |   4 +
 .../samples/ivypublish-new/subproject/build.gradle |  17 +
 .../src/main/java/org/gradle/shared/Person.java    |   5 +
 .../samples/scala/customizedLayout/build.gradle    |   8 +-
 .../docs/src/samples/scala/fsc/build.gradle        |  13 +-
 .../samples/scala/mixedJavaAndScala/build.gradle   |   8 +-
 .../docs/src/samples/scala/quickstart/build.gradle |  13 +-
 .../docs/src/samples/scala/zinc/build.gradle       |  33 +
 subprojects/docs/src/samples/scala/zinc/readme.xml |   3 +
 .../main/scala/org/gradle/sample/api/Person.scala  |   9 +
 .../scala/org/gradle/sample/impl/PersonImpl.scala  |  12 +
 .../src/main/java/org/gradle/sample/Main.java      |  35 -
 .../src/samples/toolingApi/eclipse/build.gradle    |   2 +-
 .../src/main/java/org/gradle/sample/Main.java      |  10 +
 .../idea/src/main/java/org/gradle/sample/Main.java |   4 +
 .../src/main/java/org/gradle/sample/Main.java      |   8 +
 .../toolingApi/{build => runBuild}/build.gradle    |   0
 .../toolingApi/{build => runBuild}/readme.xml      |   0
 .../src/main/java/org/gradle/sample/Main.java      |  43 ++
 .../artifacts/externalDependencies/build.gradle    |   8 +-
 .../samples/userguide/artifacts/maven/build.gradle |   1 -
 .../src/samples/userguide/files/copy/build.gradle  |   5 +-
 .../userguide/files/inputFiles/build.gradle        |   2 +-
 .../samples/userguide/java/sourceSets/build.gradle |   2 +-
 .../userguide/tutorial/projectReports/build.gradle |   2 +-
 .../tutorial/properties/gradle.properties          |   2 +-
 .../userguideOutput/dependencyInsightReport.out    |   3 +
 .../userguideOutput/dependencyListReport.out       |   8 +-
 .../samples/userguideOutput/taskListAllReport.out  |   3 +-
 .../src/samples/userguideOutput/taskListReport.out |   3 +-
 .../customised/src/test/java/org/MyClassTest.java  |   7 -
 .../src/test/java/org/gradle/MyClassTest.java      |   7 +
 subprojects/ear/ear.gradle                         |   1 +
 .../groovy/org/gradle/plugins/ear/EarPlugin.java   |   9 +-
 .../internal/DefaultDeploymentDescriptor.groovy    |  28 +-
 .../org/gradle/plugins/ear/EarPluginTest.groovy    |   3 +-
 .../groovy/org/gradle/plugins/ear/EarTest.groovy   |   3 -
 subprojects/ide/ide.gradle                         |   3 +-
 .../plugins/ide/AbstractIdeIntegrationTest.groovy  |   7 +-
 .../eclipse/EclipseClasspathIntegrationTest.groovy |  34 +-
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   4 +-
 .../ide/eclipse/EclipseIntegrationTest.groovy      |  26 +-
 .../plugins/ide/eclipse/EclipsePlugin.groovy       |  24 +-
 .../plugins/ide/eclipse/EclipseWtpPlugin.groovy    |  24 +-
 .../ide/eclipse/model/EclipseClasspath.groovy      |  74 ++-
 .../plugins/ide/eclipse/model/EclipseModel.groovy  |   2 +-
 .../org/gradle/plugins/ide/idea/IdeaPlugin.groovy  |  24 +-
 .../internal/provider/BuildModelAction.java        |  41 +-
 .../internal/provider/EclipseModelBuilder.java     |   3 +-
 .../internal/provider/FileOutcomeIdentifier.java   |  41 ++
 .../internal/provider/IdeaModelBuilder.java        |   2 +-
 .../internal/provider/MigrationModelBuilder.java   |  74 ---
 .../internal/provider/ModelBuildingAdapter.java    |  42 --
 .../internal/provider/NullResultBuilder.java       |  30 +
 .../provider/ProjectOutcomesModelBuilder.java      |  71 ++
 ...blishArtifactToFileBuildOutcomeTransformer.java |  98 +++
 .../plugins/ide/eclipse/EclipsePluginTest.groovy   |  20 +-
 .../ide/eclipse/EclipseWtpPluginTest.groovy        |   3 +-
 .../ide/idea/ GenerateIdeaModuleTest.groovy        |   7 +-
 .../gradle/plugins/ide/idea/IdeaPluginTest.groovy  |   4 +-
 ...rtifactToFileBuildOutcomeTransformerTest.groovy |  86 +++
 subprojects/integ-test/integ-test.gradle           |  18 +-
 ...kCommandLineConfigurationIntegrationSpec.groovy | 252 ++++++++
 .../DependencyReportTaskIntegrationTest.groovy     |  60 --
 .../org/gradle/debug/GradleRunConfiguration.groovy |   4 +-
 .../integtests/CacheProjectIntegrationTest.groovy  |   4 +-
 .../integtests/CommandLineIntegrationTest.groovy   |   8 +-
 .../integtests/DistributionIntegrationTest.groovy  | 160 -----
 .../DistributionLocatorIntegrationTest.groovy      |   2 +-
 .../ExternalScriptExecutionIntegrationTest.groovy  |   7 +-
 .../InitScriptExecutionIntegrationTest.groovy      |   2 +-
 .../MultiProjectDependencyIntegrationTest.groovy   | 283 ++++++++
 .../ParallelProjectExecutionIntegrationTest.groovy | 109 ++++
 .../integtests/ProjectLayoutIntegrationTest.groovy |   9 +-
 .../ProjectReportsPluginIntegrationTest.java       |  33 -
 .../integtests/WorkerProcessIntegrationTest.java   |  38 +-
 .../WrapperProjectIntegrationTest.groovy           | 172 -----
 .../BuildEnvironmentIntegrationTest.groovy         |  10 +-
 .../integtests/fixture/M2Installation.groovy       |  33 +-
 .../ivy/IvyEarProjectPublishIntegrationTest.groovy |   5 +-
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |  36 +-
 .../IvyJavaProjectPublishIntegrationTest.groovy    |   9 +-
 .../ivy/IvyLocalPublishIntegrationTest.groovy      |  41 +-
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |  12 +-
 .../IvySingleProjectPublishIntegrationTest.groovy  |   9 +-
 .../ivy/IvyWarProjectPublishIntegrationTest.groovy |   5 +-
 .../MavenEarProjectPublishIntegrationTest.groovy   |   5 +-
 .../MavenJavaProjectPublishIntegrationTest.groovy  |   5 +-
 .../MavenMultiProjectPublishIntegrationTest.groovy |   2 -
 .../MavenNewPublicationIntegrationTest.groovy      | 158 -----
 .../maven/MavenPomGenerationIntegrationTest.groovy |   8 +-
 .../maven/MavenPublishIntegrationTest.groovy       |  73 ++-
 ...MavenPublishRespectsPomConfigurationTest.groovy |   8 +-
 .../MavenWarProjectPublishIntegrationTest.groovy   |   5 +-
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  13 +-
 .../SamplesMavenQuickstartIntegrationTest.groovy   |  12 +-
 .../AbstractDependencyResolutionTest.groovy        |  42 +-
 .../ArtifactDependenciesIntegrationTest.groovy     |  12 +-
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |  11 +-
 .../resolve/CacheResolveIntegrationTest.groovy     |  58 +-
 .../ProjectDependencyResolveIntegrationTest.groovy |  21 +-
 .../ResolvedConfigurationIntegrationTest.groovy    |  19 +-
 .../VersionConflictResolutionIntegTest.groovy      | 126 +++-
 ...AliasedArtifactResolutionIntegrationTest.groovy | 171 +++--
 .../CacheReuseCrossVersionIntegrationTest.groovy   |  90 ++-
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |  23 +-
 .../MavenM2CacheReuseIntegrationTest.groovy        |  33 +-
 .../ResolutionOverrideIntegrationTest.groovy       |  45 +-
 ...achedDependencyResolutionIntegrationTest.groovy |  63 +-
 .../CachedMissingModulesIntegrationTest.groovy     | 260 ++++++++
 .../FileSystemResolverIntegrationTest.groovy       |  72 +++
 .../FilerSystemResolverIntegrationTest.groovy      |  76 ---
 .../custom/IvySFtpResolverIntegrationTest.groovy   |  18 +-
 .../custom/IvyUrlResolverIntegrationTest.groovy    |  55 +-
 .../AbstractHttpsRepoResolveIntegrationTest.groovy | 129 ++++
 ...ationDependencyResolutionIntegrationTest.groovy |  12 +-
 .../http/HttpProxyResolveIntegrationTest.groovy    |   6 +-
 .../http/HttpRedirectResolveIntegrationTest.groovy |   4 +-
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   |  38 +-
 ...angingModuleRemoteResolveIntegrationTest.groovy | 144 +++--
 ...amicRevisionRemoteResolveIntegrationTest.groovy | 464 ++++++++++---
 ...IvyDynamicRevisionResolveIntegrationTest.groovy | 334 ++++++++++
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   |  14 +-
 .../ivy/IvyHttpsRepoResolveIntegrationTest.groovy  |  28 +
 .../MavenDynamicResolveIntegrationTest.groovy      | 123 +++-
 .../MavenFileRepoResolveIntegrationTest.groovy     |   3 +-
 .../MavenHttpRepoResolveIntegrationTest.groovy     |  80 ++-
 .../MavenHttpsRepoResolveIntegrationTest.groovy    |  28 +
 .../MavenLocalRepoResolveIntegrationTest.groovy    | 108 ++--
 .../MavenPomPackagingResolveIntegrationTest.groovy |   8 +-
 .../MavenSnapshotResolveIntegrationTest.groovy     | 269 ++++----
 .../SamplesJavaApiAndImplIntegrationTest.groovy    |   5 +-
 ...mplesMultiProjectBuildSrcIntegrationTest.groovy |   7 +-
 .../samples/SamplesScalaZincIntegrationTest.groovy |  52 ++
 .../SamplesWebQuickstartIntegrationTest.groovy     |   2 +-
 .../eclipseproject/scala/expectedClasspathFile.txt |   2 +-
 .../shared/clientStore                             | Bin 0 -> 2249 bytes
 .../shared/serverStore                             | Bin 0 -> 2249 bytes
 .../build.gradle                                   |  80 ---
 .../shared/producer.gradle                         |  22 -
 .../shared/projectWithMavenSnapshots.gradle        |  20 -
 .../shared/src/main/java/org/gradle/Test.java      |   4 -
 .../internal-integ-testing.gradle                  |   3 +-
 .../fixtures/AbstractDelegatingGradleExecuter.java |   4 +
 .../fixtures/AbstractGradleExecuter.java           | 118 +++-
 .../fixtures/AbstractIntegrationSpec.groovy        |  49 +-
 .../fixtures/AbstractIntegrationTest.java          |  37 +-
 .../fixtures/AbstractMultiTestRunner.java          |  59 +-
 .../integtests/fixtures/AvailableJavaHomes.java    | 106 ++-
 .../fixtures/BasicGradleDistribution.java          |   5 +
 .../gradle/integtests/fixtures/ClassFile.groovy    |  69 ++
 .../fixtures/CrossVersionIntegrationSpec.groovy    |  13 +
 .../integtests/fixtures/DaemonGradleExecuter.java  |  60 +-
 .../fixtures/EmbeddedDaemonGradleExecuter.java     |   5 +-
 .../integtests/fixtures/ExecutionResult.java       |   2 +
 .../integtests/fixtures/ForkingGradleExecuter.java |  49 +-
 .../integtests/fixtures/ForkingGradleHandle.java   |   4 +-
 .../integtests/fixtures/GradleDistribution.java    |  32 +-
 .../fixtures/GradleDistributionExecuter.java       | 130 ++--
 .../gradle/integtests/fixtures/GradleExecuter.java |  61 +-
 .../gradle/integtests/fixtures/GradleHandle.java   |   4 -
 .../gradle/integtests/fixtures/HttpServer.groovy   | 639 ------------------
 .../fixtures/InProcessGradleExecuter.java          |  57 +-
 .../integtests/fixtures/IvyRepository.groovy       | 293 ---------
 .../fixtures/JUnitTestExecutionResult.groovy       |  18 +-
 .../fixtures/KillProcessAvailability.groovy        |  47 ++
 .../integtests/fixtures/MavenFileModule.groovy     | 294 +++++++++
 .../integtests/fixtures/MavenFileRepository.groovy |  41 ++
 .../integtests/fixtures/MavenHttpModule.groovy     | 152 +++++
 .../integtests/fixtures/MavenHttpRepository.groovy |  70 ++
 .../gradle/integtests/fixtures/MavenModule.groovy  |  42 ++
 .../org/gradle/integtests/fixtures/MavenPom.groovy |  46 ++
 .../integtests/fixtures/MavenRepository.groovy     | 408 +-----------
 .../gradle/integtests/fixtures/MavenScope.groovy   |  36 ++
 .../fixtures/MultiVersionIntegrationSpec.groovy    |   5 +
 .../fixtures/OutputScrapingExecutionResult.java    |   9 +-
 .../fixtures/OutputScrapingGradleHandle.java       |  13 +-
 .../fixtures/ParallelForkingGradleExecuter.java    |  43 ++
 .../fixtures/ParallelForkingGradleHandle.java      |  76 +++
 .../fixtures/ParallelOutputMatcher.groovy          |  51 ++
 .../fixtures/PreviousGradleVersionExecuter.groovy  |  24 +-
 .../fixtures/ProgressLoggingFixture.groovy         |  83 +++
 .../fixtures/RedirectMavenCentral.groovy           |  46 ++
 .../integtests/fixtures/ReleasedVersions.java      |  14 -
 .../gradle/integtests/fixtures/SFTPServer.groovy   | 153 -----
 .../fixtures/SequentialOutputMatcher.groovy        |  92 +++
 .../fixtures/TestClassExecutionResult.java         |   2 +
 .../fixtures/TestNGExecutionResult.groovy          |  37 +-
 .../fixtures/TestNativeFileSystem.groovy           | 111 ----
 .../integtests/fixtures/TestProxyServer.groovy     |  70 --
 .../fixtures/UserGuideSamplesRunner.groovy         |  88 +--
 .../fixtures/WellBehavedPluginTest.groovy          |   2 +-
 .../test/fixtures/ivy/AbstractIvyModule.groovy     |  25 +
 .../gradle/test/fixtures/ivy/IvyDescriptor.groovy  |  70 ++
 .../test/fixtures/ivy/IvyDescriptorArtifact.groovy |  25 +
 .../fixtures/ivy/IvyDescriptorConfiguration.groovy |  26 +
 .../IvyDescriptorDependencyConfiguration.groovy    |  32 +
 .../gradle/test/fixtures/ivy/IvyFileModule.groovy  | 203 ++++++
 .../test/fixtures/ivy/IvyFileRepository.groovy     |  71 ++
 .../gradle/test/fixtures/ivy/IvyHttpModule.groovy  | 132 ++++
 .../test/fixtures/ivy/IvyHttpRepository.groovy     |  54 ++
 .../org/gradle/test/fixtures/ivy/IvyModule.java    |  50 ++
 .../gradle/test/fixtures/ivy/IvyRepository.groovy  |  32 +
 .../fixtures/server/http/BlockingHttpServer.groovy | 133 ++++
 .../test/fixtures/server/http/HttpServer.groovy    | 684 ++++++++++++++++++++
 .../fixtures/server/http/TestProxyServer.groovy    |  72 +++
 .../test/fixtures/server/sftp/SFTPServer.groovy    | 242 +++++++
 .../org/gradle/test/matchers/UserAgentMatcher.java |  54 ++
 .../src/main/resources/logback.xml                 |  14 +
 .../internal-testing/internal-testing.gradle       |   6 +-
 .../src/main/groovy/org/gradle/util/TestFile.java  |   4 +-
 .../groovy/org/gradle/util/TestFileHelper.groovy   |   3 +
 .../groovy/org/gradle/util/TestPrecondition.groovy |   3 +
 subprojects/ivy/ivy.gradle                         |  29 +
 .../ivy/AutoTestedSamplesIvyIntegrationTest.groovy |  29 +
 .../ivy/IvyCustomPublishIntegrationTest.groovy     |  67 ++
 .../ivy/IvyEarProjectPublishIntegrationTest.groovy |  64 ++
 .../ivy/IvyHttpPublishIntegrationTest.groovy       | 349 ++++++++++
 .../IvyJavaProjectPublishIntegrationTest.groovy    |  62 ++
 .../ivy/IvyLocalPublishIntegrationTest.groovy      | 128 ++++
 ...vyPublishDescriptorModificationIntegTest.groovy |  85 +++
 .../IvyPublishMultipleReposIntegrationTest.groovy  |  96 +++
 .../publish/ivy/IvyPublishPluginIntegTest.groovy   |  32 +
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |  97 +++
 .../IvySingleProjectPublishIntegrationTest.groovy  | 137 ++++
 .../ivy/IvyWarProjectPublishIntegrationTest.groovy |  60 ++
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |  73 +++
 .../api/publish/ivy/IvyModuleDescriptor.java       |  89 +++
 .../org/gradle/api/publish/ivy/IvyPublication.java | 103 +++
 .../ivy/internal/DefaultIvyModuleDescriptor.java   |  45 ++
 .../ivy/internal/DefaultIvyPublication.java        | 100 +++
 .../ivy/internal/IvyModuleDescriptorInternal.java  |  26 +
 .../ivy/internal/IvyPublicationInternal.java       |  31 +
 .../org/gradle/api/publish/ivy/package-info.java   |  25 +
 .../api/publish/ivy/plugins/IvyPublishPlugin.java  | 101 +++
 .../api/publish/ivy/plugins/package-info.java      |  25 +
 .../publish/ivy/tasks/PublishToIvyRepository.java  | 167 +++++
 .../internal/IvyPublishDynamicTaskCreator.java     |  95 +++
 .../gradle/api/publish/ivy/tasks/package-info.java |  25 +
 .../META-INF/gradle-plugins/ivy-publish.properties |   1 +
 .../ivy/internal/DefaultIvyPublicationTest.groovy  |  81 +++
 .../ivy/plugins/IvyPublishPluginTest.groovy        |  85 +++
 .../ivy/tasks/PublishToIvyRepositoryTest.groovy    | 143 ++++
 .../IvyPublishDynamicTaskCreatorTest.groovy        | 105 +++
 subprojects/javascript/javascript.gradle           |   3 +-
 .../javascript/base/JavaScriptBasePlugin.groovy    |  15 +-
 .../javascript/base/JavaScriptExtension.java       |  12 +-
 .../coffeescript/CoffeeScriptCompile.java          |  10 +-
 .../plugins/javascript/envjs/EnvJsPlugin.groovy    |  11 +-
 .../gradle/plugins/javascript/jshint/JsHint.java   |  10 +-
 .../base/JavaScriptBasePluginTest.groovy           |  35 +-
 subprojects/launcher/launcher.gradle               |   6 +-
 .../GradleConfigurabilityIntegrationSpec.groovy    | 109 ++++
 .../DaemonConfigurabilityIntegrationSpec.groovy    | 100 ---
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |  42 +-
 ...itialCommunicationFailureIntegrationSpec.groovy | 128 ++++
 .../launcher/daemon/DaemonIntegrationSpec.groovy   |  10 +-
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |  21 +-
 .../DaemonSystemPropertiesIntegrationTest.groovy   |  49 ++
 .../daemon/StoppingDaemonIntegrationSpec.groovy    |  70 ++
 .../StoppingDaemonSmokeIntegrationSpec.groovy      |  89 ---
 .../daemon/testing/DaemonContextParser.java        |   3 +-
 .../testing/DaemonEventSequenceBuilder.groovy      |   2 +-
 .../daemon/testing/DaemonLogsAnalyzer.groovy       |  52 ++
 .../launcher/daemon/testing/TestableDaemon.groovy  | 116 ++++
 .../org/gradle/launcher/cli/ActionAdapter.java     |  36 --
 .../gradle/launcher/cli/BuildActionsFactory.java   |  23 +-
 .../org/gradle/launcher/cli/CommandLineAction.java |   2 +-
 .../launcher/cli/CommandLineActionFactory.java     |  15 +-
 .../org/gradle/launcher/cli/GuiActionsFactory.java |   5 +-
 .../launcher/daemon/bootstrap/DaemonMain.java      |  43 +-
 .../daemon/bootstrap/ForegroundDaemonMain.java     |   3 +-
 .../launcher/daemon/client/DaemonClient.java       | 136 ++--
 .../daemon/client/DaemonClientConnection.java      |  74 +++
 .../daemon/client/DaemonClientServicesSupport.java |   2 +-
 .../launcher/daemon/client/DaemonConnection.java   |  56 --
 .../launcher/daemon/client/DaemonConnector.java    |   6 +-
 .../client/DaemonInitialConnectException.java      |   4 -
 .../daemon/client/DefaultDaemonConnector.java      |  58 +-
 .../daemon/client/DefaultDaemonStarter.java        |   4 +-
 .../client/EmbeddedDaemonClientServices.java       |   4 +-
 .../daemon/client/EmbeddedDaemonStarter.java       |   2 +-
 .../launcher/daemon/client/InputForwarder.java     |   2 +-
 .../daemon/client/SingleUseDaemonClient.java       |   6 +-
 .../launcher/daemon/client/StopDispatcher.java     |  33 +-
 .../daemon/diagnostics/DaemonDiagnostics.java      |   2 +-
 .../launcher/daemon/logging/DaemonMessages.java    |  10 +-
 .../launcher/daemon/protocol/DaemonBusy.java       |  28 -
 .../daemon/protocol/DaemonUnavailable.java         |  34 +
 .../gradle/launcher/daemon/protocol/Finished.java  |  22 +
 .../launcher/daemon/registry/DaemonInfo.java       |   5 +-
 .../launcher/daemon/registry/DaemonRegistry.java   |   2 +-
 .../daemon/registry/DaemonRegistryServices.java    |   2 +-
 .../daemon/registry/EmbeddedDaemonRegistry.java    |   4 +-
 .../daemon/registry/PersistentDaemonRegistry.java  |   8 +-
 .../org/gradle/launcher/daemon/server/Daemon.java  | 157 ++---
 .../daemon/server/DaemonServerConnector.java       |   2 +-
 .../launcher/daemon/server/DaemonServices.java     |   3 +-
 .../daemon/server/DaemonStateCoordinator.java      | 343 ++++------
 .../daemon/server/DaemonStoppedException.java      |  18 +-
 .../daemon/server/DefaultDaemonConnection.java     | 369 +++++++++++
 .../server/DefaultIncomingConnectionHandler.java   | 172 +++++
 .../daemon/server/DomainRegistryUpdater.java       |  19 +-
 .../server/SynchronizedDispatchConnection.java     |  13 +-
 .../server/exec/CatchAndForwardDaemonFailure.java  |   2 +-
 .../daemon/server/exec/DaemonCommandExecuter.java  |   3 +-
 .../daemon/server/exec/DaemonCommandExecution.java |  13 +-
 .../daemon/server/exec/DaemonConnection.java       |  90 +++
 .../daemon/server/exec/DaemonStateControl.java     |  42 +-
 .../server/exec/DaemonUnavailableException.java    |  27 +
 .../server/exec/DefaultDaemonCommandExecuter.java  |  25 +-
 .../server/exec/EstablishBuildEnvironment.java     |  25 +-
 .../daemon/server/exec/ForwardClientInput.java     |  95 +--
 ...HandleClientDisconnectBeforeSendingCommand.java |  24 -
 .../launcher/daemon/server/exec/HandleStop.java    |   6 +-
 .../launcher/daemon/server/exec/LogToClient.java   |  14 +-
 .../launcher/daemon/server/exec/ReturnResult.java  |   2 +-
 .../server/exec/StartBuildOrRespondWithBusy.java   |  34 +-
 .../server/exec/StartStopIfBuildAndStop.java       |  11 +-
 .../launcher/daemon/server/exec/StdinHandler.java  |  25 +
 .../server/exec/StopConnectionAfterExecution.java  |  40 --
 .../daemon/server/exec/WatchForDisconnection.java  |  14 +-
 .../internal/provider/ConfiguringBuildAction.java  |   5 +-
 .../DaemonGradleLauncherActionExecuter.java        |   2 +-
 .../internal/provider/DefaultConnection.java       |  80 ++-
 .../provider/DelegatingBuildModelAction.java       |   6 +-
 ...oggingBridgingGradleLauncherActionExecuter.java |   2 +-
 .../connection/AdaptedOperationParameters.java     | 126 ++++
 .../provider/connection/BuildLogLevelMixIn.java    |  48 ++
 .../provider/connection/ProviderBuildResult.java   |  31 +
 .../connection/ProviderConnectionParameters.java   |  23 +
 .../connection/ProviderOperationParameters.java    |  67 ++
 .../provider/input/AdaptedOperationParameters.java | 150 -----
 .../input/ProviderOperationParameters.java         |  66 --
 .../launcher/cli/BuildActionsFactoryTest.groovy    |  34 +-
 .../launcher/cli/GuiActionsFactoryTest.groovy      |   6 +-
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |   4 +-
 .../client/DaemonClientConnectionTest.groovy       |  94 +++
 .../client/DaemonClientInputForwarderTest.groovy   |  28 +-
 .../launcher/daemon/client/DaemonClientTest.groovy | 110 +++-
 .../client/DefaultDaemonConnectorTest.groovy       |  16 +-
 .../registry/DaemonRegistryServicesTest.groovy     |   2 +-
 .../registry/DomainRegistryUpdaterTest.groovy      |  21 +-
 .../registry/EmbeddedDaemonRegistrySpec.groovy     |   4 +-
 .../registry/PersistentDaemonRegistryTest.groovy   |  45 +-
 .../DaemonServerExceptionHandlingTest.groovy       |   3 +-
 .../server/DaemonStateCoordinatorTest.groovy       | 427 ++++++++++--
 .../server/DefaultDaemonConnectionTest.groovy      | 394 +++++++++++
 .../DaemonGradleLauncherActionExecuterTest.groovy  |   2 +-
 ...BridgingGradleLauncherActionExecuterTest.groovy |   2 +-
 .../AdaptedOperationParametersTest.groovy          |  71 ++
 .../input/AdaptedOperationParametersTest.groovy    |  71 --
 .../maven/MavenConversionIntegrationTest.groovy    | 139 ++++
 .../flatmultimodule/webinar-api/pom.xml            |  20 +
 .../src/main/java/webinar/Demoable.java            |   5 +
 .../flatmultimodule/webinar-impl/pom.xml           |  39 ++
 .../src/main/java/webinar/Webinar.java             |  20 +
 .../src/test/java/webinar/WebinarTest.java         |  15 +
 .../flatmultimodule/webinar-parent/pom.xml         |  30 +
 .../flatmultimodule/webinar-war/pom.xml            |  37 ++
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |   7 +
 .../webinar-war/src/main/webapp/index.jsp          |   6 +
 .../multiModule/pom.xml                            |  30 +
 .../multiModule/webinar-api/pom.xml                |  19 +
 .../src/main/java/webinar/Demoable.java            |   5 +
 .../multiModule/webinar-impl/pom.xml               |  38 ++
 .../src/main/java/webinar/Webinar.java             |  20 +
 .../src/test/java/webinar/WebinarTest.java         |  15 +
 .../multiModule/webinar-war/pom.xml                |  36 ++
 .../webinar-war/src/main/webapp/WEB-INF/web.xml    |   7 +
 .../webinar-war/src/main/webapp/index.jsp          |   6 +
 .../singleModule/pom.xml                           |  23 +
 .../singleModule/src/main/java/Foo.java            |   7 +
 .../singleModule/src/test/java/FooTest.java        |   7 +
 .../MavenConversionIntegrationTest/testjar/pom.xml |  38 ++
 .../testjar/src/main/java/Foo.java                 |   7 +
 .../testjar/src/test/java/FooTest.java             |   8 +
 .../groovy/org/gradle/api/plugins/MavenPlugin.java |  12 +-
 .../api/plugins/maven/ConvertMaven2Gradle.groovy   |  67 ++
 .../api/plugins/maven/Maven2GradlePlugin.groovy    |  36 ++
 .../api/plugins/maven/internal/Maven2Gradle.groovy | 552 ++++++++++++++++
 .../maven/internal/MavenProjectXmlWriter.java      |  58 ++
 .../maven/internal/MavenProjectsCreator.java       |  98 +++
 .../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 -
 .../maven/internal/DefaultMavenPom.java            |  50 +-
 .../internal/MavenPublicationPomGenerator.groovy   |  58 --
 .../maven/internal/ant/AbstractMavenResolver.java  |  18 +-
 .../internal/ant/DefaultMavenPublisher.groovy      |  93 ---
 .../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 ---
 .../gradle-plugins/maven2Gradle.properties         |   1 +
 .../org/gradle/api/plugins/MavenPluginTest.java    |  21 +-
 .../plugins/maven/Maven2GradlePluginSpec.groovy    |  36 ++
 .../maven/internal/MavenProjectsCreatorSpec.groovy | 118 ++++
 .../internal/ant/DefaultMavenPublisherTest.groovy  | 150 -----
 .../MavenPublicationBuilderTest.groovy             | 209 ------
 .../actor/internal/DefaultActorFactory.java        |   4 +-
 .../remote/internal/AsyncConnectionAdapter.java    |   2 +-
 .../remote/internal/DefaultIncomingBroadcast.java  |   2 +-
 .../remote/internal/DefaultMessagingClient.java    |   2 +-
 .../remote/internal/DefaultMessagingServer.java    |   2 +-
 .../remote/internal/DefaultOutgoingBroadcast.java  |   4 +-
 .../remote/internal/DisconnectAwareConnection.java |  46 --
 .../DisconnectAwareConnectionDecorator.java        | 123 ----
 .../remote/internal/EagerReceiveBuffer.java        |  25 +-
 .../messaging/remote/internal/MessageHub.java      |   4 +-
 .../messaging/remote/internal/ProtocolStack.java   |   2 +-
 .../remote/internal/inet/SocketConnection.java     |   4 +-
 .../remote/internal/inet/TcpIncomingConnector.java |   4 +-
 .../remote/internal/protocol/MethodMetaInfo.java   |  16 +
 .../internal/protocol/RemoteMethodInvocation.java  |   9 +
 .../DisconnectAwareConnectionDecoratorTest.groovy  | 168 -----
 .../remote/internal/inet/TcpConnectorTest.groovy   |  38 +-
 subprojects/migration/migration.gradle             |  26 -
 subprojects/native/native.gradle                   |   7 +-
 .../nativeplatform/NoOpTerminalDetector.java       |  25 -
 .../internal/nativeplatform/TerminalDetector.java  |  23 -
 .../nativeplatform/WindowsTerminalDetector.java    |  36 --
 .../nativeplatform/console/ConsoleDetector.java    |  29 +
 .../nativeplatform/console/ConsoleMetaData.java    |  36 ++
 .../console/FallbackConsoleMetaData.java           |  31 +
 .../console/NativePlatformConsoleDetector.java     |  48 ++
 .../console/NativePlatformConsoleMetaData.java     |  43 ++
 .../console/NoOpConsoleDetector.java               |  23 +
 .../console/UnixConsoleMetaData.java               |  51 ++
 .../console/WindowsConsoleDetector.java            |  35 +
 .../internal/nativeplatform/filesystem/Chmod.java  |  11 +-
 .../nativeplatform/filesystem/FileSystem.java      |  14 +-
 .../filesystem/FileSystemServices.java             |   6 +-
 .../nativeplatform/jna/JnaBootPathConfigurer.java  |  16 +-
 .../jna/LibCBackedConsoleDetector.java             |  64 ++
 .../jna/LibCBackedTerminalDetector.java            |  56 --
 .../jna/WindowsProcessEnvironment.java             |   2 +-
 .../nativeplatform/services/NativeServices.java    |  92 ++-
 .../NativePlatformConsoleDetectorTest.groovy       |  67 ++
 .../jna/LibCBackedProcessEnvironmentTest.groovy    |   2 +-
 .../jna/ProcessEnvironmentTest.groovy              |   2 +-
 .../services/NativeServicesTest.groovy             |  10 +-
 subprojects/open-api/open-api.gradle               |   3 +
 .../plugins/osgi/OsgiPluginIntegrationSpec.groovy  |   8 +-
 .../internal/plugins/osgi/DefaultOsgiManifest.java | 236 ++++++-
 .../plugins/osgi/OsgiPluginConventionTest.groovy   |  16 +-
 subprojects/performance/performance.gradle         |  64 +-
 subprojects/performance/src/generator.groovy       |  24 +-
 .../DependencyResolutionStressTest.groovy          | 280 ++++++++
 .../org/gradle/peformance/PerformanceTest.groovy   | 124 +++-
 .../peformance/fixture/MeasuredOperation.groovy    |  47 --
 .../peformance/fixture/PerformanceResults.groovy   |  69 --
 .../fixture/PerformanceTestRunner.groovy           |  79 ---
 .../peformance/fixture/TestProjectLocator.groovy   |  38 --
 .../performance/src/templates/Production.scala     |   7 +
 subprojects/performance/src/templates/Test.scala   |  12 +
 .../performance/src/templates/TestNGTest.java      |  42 ++
 .../src/templates/VerboseJUnitTest.java            |  26 +
 subprojects/performance/src/templates/build.gradle | 126 +++-
 .../gradle/peformance/fixture/AmountTest.groovy    | 231 +++++++
 .../gradle/peformance/fixture/DurationTest.groovy  |  59 ++
 .../fixture/PerformanceResultsTest.groovy          | 202 ++++++
 .../peformance/fixture/PrettyCalculatorSpec.groovy |  45 ++
 .../org/gradle/peformance/fixture/UnitsTest.groovy |  34 +
 .../org/gradle/peformance/fixture/Amount.java      | 160 +++++
 .../org/gradle/peformance/fixture/DataAmount.java  |  37 ++
 .../gradle/peformance/fixture/DataCollector.java   |  27 +
 .../org/gradle/peformance/fixture/Duration.java    |  45 ++
 .../peformance/fixture/MeasuredOperation.groovy    |  41 ++
 .../fixture/MeasuredOperationList.groovy           |  50 ++
 .../peformance/fixture/MemoryInfoCollector.groovy  |  34 +
 .../peformance/fixture/PerformanceResults.groovy   | 136 ++++
 .../fixture/PerformanceTestRunner.groovy           |  89 +++
 .../peformance/fixture/PrettyCalculator.groovy     |  49 ++
 .../peformance/fixture/TestProjectLocator.groovy   |  38 ++
 .../org/gradle/peformance/fixture/Units.java       | 193 ++++++
 subprojects/plugins/plugins.gradle                 |  10 +-
 .../api/plugins/BasePluginIntegrationTest.groovy   |   2 +-
 .../gradle/api/plugins/BuildSrcPluginTest.groovy   |   5 +-
 .../GitHubDependenciesPluginIntegrationTest.groovy |  55 ++
 .../ParallelCompilerDaemonIntegrationTest.groovy   |  61 ++
 .../AntForkingGroovyCompilerIntegrationTest.groovy |   5 +-
 ...ntInProcessGroovyCompilerIntegrationTest.groovy |   4 +-
 .../BasicGroovyCompilerIntegrationSpec.groovy      |  33 +-
 .../DaemonGroovyCompilerIntegrationTest.groovy     |   9 +-
 .../compile/InvokeDynamicGroovyCompilerSpec.groovy |   2 +-
 .../JreJavaHomeGroovyIntegrationTest.groovy        |  14 +-
 .../BasicJavaCompilerIntegrationSpec.groovy        |   9 +-
 .../org/gradle/java/compile/ClassFile.groovy       |  68 --
 .../compile/JavaCompilerIntegrationSpec.groovy     |   2 +-
 .../DaemonJavaCompilerIntegrationTest.groovy       |   7 +-
 .../JreJavaHomeJavaIntegrationTest.groovy          |   9 +-
 .../gradle/testing/TestingIntegrationTest.groovy   |  56 +-
 .../testing/junit/JUnitIntegrationTest.groovy      |  43 +-
 ...NGProducesJUnitXmlResultsIntegrationTest.groovy | 164 +++++
 .../TestNGProducesOldReportsIntegrationTest.groovy | 131 ++++
 .../shared/GroovyClass.groovy                      |  96 +++
 .../shared/JavaClass.java                          | 106 +++
 .../shared/build.gradle                            |  14 +
 .../build.gradle                                   |   9 +
 .../test/groovy/org/gradle/SystemErrTest.groovy    |  32 +
 .../build.gradle                                   |   9 +
 .../test/groovy/org/gradle/SystemOutTest.groovy    |  32 +
 .../shared/build.gradle                            |   2 +-
 .../standardOutputLogging/build.gradle             |   2 +-
 .../groovyJdk15Failing/build.gradle                |   2 +-
 .../groovyJdk15Passing/build.gradle                |   2 +-
 .../supportsTestGroups/build.gradle                |   8 +-
 .../shared/build.gradle                            |   2 +-
 .../standardOutputLogging/build.gradle             |   2 +-
 .../plugins/DefaultArtifactPublicationSet.java     |   2 +-
 .../api/internal/tasks/DefaultSourceSet.java       |   4 +
 .../tasks/compile/AntGroovyCompiler.groovy         |  28 +-
 .../CommandLineJavaCompilerArgumentsGenerator.java |   5 +-
 .../tasks/compile/CompilationFailedException.java  |   4 +
 .../compile/DefaultGroovyJavaJointCompileSpec.java |   6 +-
 .../tasks/compile/DefaultJavaCompileSpec.java      |  15 +-
 .../tasks/compile/DefaultJavaCompilerFactory.java  |  14 +-
 .../compile/DefaultJvmLanguageCompileSpec.java     |   8 +
 .../tasks/compile/GroovyCompilerFactory.java       |  52 +-
 .../compile/JavaCompilerArgumentsBuilder.java      |  18 +-
 .../tasks/compile/JvmLanguageCompileSpec.java      |   4 +
 .../tasks/compile/NoOpStaleClassCleaner.java       |  24 +
 .../tasks/compile/TransformingClassLoader.java     |  60 +-
 .../tasks/compile/daemon/CompilerDaemonClient.java |  26 +-
 .../compile/daemon/CompilerDaemonManager.java      |  80 +--
 .../detection/AbstractTestFrameworkDetector.java   |  27 +-
 .../testing/detection/DefaultTestExecuter.java     |   4 +-
 .../tasks/testing/detection/TestClassVisitor.java  |   6 +-
 .../testing/detection/TestFrameworkDetector.java   |   5 +
 .../tasks/testing/junit/JUnitDetector.java         |   7 +-
 .../testing/junit/JUnitTestClassDetecter.java      |  44 +-
 .../tasks/testing/junit/JUnitTestFramework.java    |   6 +-
 .../testing/junit/JUnitTestMethodDetecter.java     |  16 +-
 .../testing/junit/JUnitXmlReportGenerator.java     | 122 +---
 .../junit/TestNGJUnitXmlReportGenerator.java       |  99 +++
 .../testing/junit/report/ClassPageRenderer.java    |   4 +-
 .../testing/junit/report/OverviewPageRenderer.java |  16 +-
 .../testing/junit/report/PackagePageRenderer.java  |   8 +-
 .../tasks/testing/junit/result/XmlTestSuite.java   | 150 +++++
 .../testing/junit/result/XmlTestSuiteFactory.java  |  51 ++
 .../tasks/testing/logging/AbstractTestLogger.java  |  10 +-
 .../processors/MaxNParallelTestClassProcessor.java |   2 +-
 .../tasks/testing/testng/TestNGDetector.java       |  19 +-
 .../testing/testng/TestNGTestClassDetecter.java    |  44 +-
 .../testing/testng/TestNGTestClassProcessor.java   |  41 +-
 .../tasks/testing/testng/TestNGTestFramework.java  |  39 +-
 .../testing/testng/TestNGTestMethodDetecter.java   |  16 +-
 .../java/archives/internal/DefaultManifest.java    |  21 +-
 .../groovy/org/gradle/api/plugins/BasePlugin.java  |   2 +-
 .../org/gradle/api/plugins/JavaBasePlugin.java     |  29 +-
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |   4 +-
 .../gradle/api/plugins/JavaPluginConvention.groovy |   6 +-
 .../plugins/ProjectReportsPluginConvention.groovy  |  43 --
 .../gradle/api/plugins/ReportingBasePlugin.java    |  43 --
 .../plugins/github/GitHubDependenciesPlugin.groovy |  63 ++
 .../plugins/github/GitHubDownloadsRepository.java  |  85 +++
 .../github/GitHubRepositoryHandlerExtension.java   |  53 ++
 .../internal/DefaultGitHubDownloadsRepository.java | 119 ++++
 .../org/gradle/api/reporting/package-info.java     |  20 -
 .../groovy/org/gradle/api/tasks/SourceSet.java     |   7 +
 .../gradle/api/tasks/compile/AbstractOptions.java  |  75 +--
 .../gradle/api/tasks/compile/BaseForkOptions.java  |  94 +++
 .../org/gradle/api/tasks/compile/Compile.java      |  12 +-
 .../gradle/api/tasks/compile/CompileOptions.java   |  87 ++-
 .../org/gradle/api/tasks/compile/DebugOptions.java |  10 -
 .../gradle/api/tasks/compile/DependOptions.java    |  14 +-
 .../org/gradle/api/tasks/compile/ForkOptions.java  |  80 +--
 .../gradle/api/tasks/compile/GroovyCompile.java    |  11 +-
 .../api/tasks/compile/GroovyCompileOptions.java    |  56 +-
 .../api/tasks/compile/GroovyForkOptions.java       |  72 +--
 .../org/gradle/api/tasks/compile/JavaCompile.java  |  23 +
 .../groovy/org/gradle/api/tasks/testing/Test.java  |  28 +-
 .../tasks/testing/logging/TestExceptionFormat.java |   3 -
 .../api/tasks/testing/logging/TestLogEvent.java    |   3 -
 .../api/tasks/testing/logging/TestLogging.java     |  94 +--
 .../testing/logging/TestLoggingContainer.java      |   2 -
 .../testing/logging/TestStackTraceFilter.java      |   3 -
 .../api/tasks/testing/logging/package-info.java    |   2 -
 .../api/tasks/testing/testng/TestNGOptions.groovy  |  14 +-
 .../internal/GroupsJavadocOptionFileOption.java    |   4 +-
 .../javadoc/internal/JavadocOptionFileWriter.java  |  37 +-
 .../gradle-plugins/github-dependencies.properties  |   1 +
 .../api/internal/plugins/unixStartScript.txt       |   6 +-
 .../api/internal/tasks/DefaultSourceSetTest.groovy |   3 +-
 ...ndLineJavaCompilerArgumentsGeneratorTest.groovy |   2 +
 .../JavaCompilerArgumentsBuilderTest.groovy        |  32 +
 .../compile/NormalizingGroovyCompilerTest.groovy   |   7 +-
 .../compile/NormalizingJavaCompilerTest.groovy     |   5 +-
 .../detection/DefaultTestExecuterTest.groovy       |  68 ++
 .../testng/TestNGTestClassProcessorTest.groovy     |  30 +-
 .../testing/testng/TestNGTestFrameworkTest.java    |   6 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |   9 +-
 .../api/plugins/JavaPluginConventionTest.groovy    |   5 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |  47 +-
 .../api/plugins/ReportingBasePluginTest.groovy     |  47 --
 .../github/GitHubDependenciesPluginTest.groovy     |  51 ++
 .../internal/DefaultReportContainerTest.groovy     | 132 ----
 .../org/gradle/api/tasks/bundling/JarTest.groovy   |   1 -
 .../org/gradle/api/tasks/bundling/WarTest.groovy   |   3 -
 .../api/tasks/compile/AbstractOptionsTest.groovy   |  40 +-
 .../api/tasks/compile/CompileOptionsTest.groovy    |   6 +-
 .../org/gradle/api/tasks/compile/CompileTest.java  |  90 ---
 .../api/tasks/compile/DebugOptionsTest.groovy      |  28 +-
 .../api/tasks/compile/ForkOptionsTest.groovy       |  15 +-
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |   4 +-
 .../api/tasks/compile/GroovyCompileTest.java       |   1 -
 .../gradle/api/tasks/compile/JavaCompileTest.java  |  89 +++
 .../gradle/api/tasks/javadoc/GroovydocTest.java    |   1 -
 .../org/gradle/api/tasks/javadoc/JavadocTest.java  |   1 -
 .../org/gradle/api/tasks/testing/TestTest.java     |   2 -
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |   1 -
 .../internal/JavadocOptionFileWriterTest.groovy    |  65 ++
 .../api/tasks/compile/AbstractCompileTest.java     |   6 +-
 subprojects/publish/publish.gradle                 |  23 +
 .../PublishAutoTestedSamplesIntegrationTest.groovy |  31 +
 .../plugins/PublishingPluginIntegTest.groovy       |  28 +
 .../java/org/gradle/api/publish/Publication.java   |  30 +
 .../gradle/api/publish/PublicationContainer.java   |  46 ++
 .../gradle/api/publish/PublishingExtension.java    | 101 +++
 .../api/publish/UnknownPublicationException.java   |  32 +
 .../internal/DefaultPublicationContainer.java      |  36 ++
 .../internal/DefaultPublishingExtension.java       |  49 ++
 .../java/org/gradle/api/publish/package-info.java  |  25 +
 .../api/publish/plugins/PublishingPlugin.java      |  59 ++
 .../gradle/api/publish/plugins/package-info.java   |  25 +
 .../META-INF/gradle-plugins/publishing.properties  |   1 +
 .../DefaultPublicationContainerTest.groovy         |  71 ++
 .../publish/plugins/PublishingPluginTest.groovy    |  75 +++
 subprojects/reporting/reporting.gradle             |   6 +
 .../gradle/api/plugins/ReportingBasePlugin.java    |  41 ++
 .../api/plugins/ReportingBasePluginConvention.java |   0
 .../groovy/org/gradle/api/reporting/Report.java    |   0
 .../org/gradle/api/reporting/ReportContainer.java  |   0
 .../groovy/org/gradle/api/reporting/Reporting.java |   0
 .../gradle/api/reporting/ReportingExtension.java   |   0
 .../org/gradle/api/reporting/SingleFileReport.java |   0
 .../reporting/internal/DefaultReportContainer.java |   0
 .../api/reporting/internal/SimpleReport.java       |   0
 .../reporting/internal/TaskGeneratedReport.java    |   0
 .../internal/TaskGeneratedSingleFileReport.java    |   0
 .../reporting/internal/TaskReportContainer.java    |   0
 .../org/gradle/api/reporting/package-info.java     |  20 +
 .../api/reporting/ReportingExtensionTest.groovy    |   0
 .../internal/DefaultReportContainerTest.groovy     | 137 ++++
 .../internal/TaskGeneratedReportTest.groovy        |   0
 .../internal/TaskReportContainerTest.groovy        |   0
 subprojects/scala/scala.gradle                     |  20 +-
 .../AntForkingScalaCompilerIntegrationTest.groovy  |  43 ++
 ...AntInProcessScalaCompilerIntegrationTest.groovy |  42 ++
 .../BasicScalaCompilerIntegrationTest.groovy       | 225 +++++++
 .../ZincScalaCompilerIntegrationTest.groovy        |  50 ++
 ...tForkingScalaCompilerJdk6IntegrationTest.groovy |  44 ++
 ...nProcessScalaCompilerJdk6IntegrationTest.groovy |  43 ++
 .../ZincScalaCompilerJdk6IntegrationTest.groovy    |  96 +++
 .../JreJavaHomeScalaIntegrationTest.groovy         |  16 +-
 .../recompilesDependentClasses/build.gradle        |   5 +-
 .../build.gradle                                   |   6 +-
 .../build.gradle                                   |  26 +
 .../prj1/src/main/scala/Person.scala               |   1 +
 .../prj2/src/main/scala/House.scala                |   1 +
 .../prj2/src/main/scala/Other.scala                |   2 +
 .../settings.gradle                                |   2 +
 .../compilesJavaCodeIncrementally/build.gradle     |  18 +
 .../src/main/scala/House.java                      |  13 +
 .../src/main/scala/Other.java                      |   1 +
 .../src/main/scala/Person.java                     |  17 +
 .../compilesScalaCodeIncrementally/build.gradle    |  18 +
 .../src/main/scala/House.scala                     |   1 +
 .../src/main/scala/Other.scala                     |   2 +
 .../src/main/scala/Person.scala                    |   1 +
 .../internal/tasks/scala/AntScalaCompiler.groovy   |  50 +-
 .../internal/tasks/scala/DaemonScalaCompiler.java  |  86 +++
 .../tasks/scala/DefaultScalaCompileSpec.java       |  19 +
 .../scala/DefaultScalaJavaJointCompileSpec.java    |  25 +-
 .../tasks/scala/DelegatingScalaCompiler.java       |  33 +
 .../tasks/scala/IncrementalScalaCompiler.java      |   9 +-
 .../tasks/scala/NormalizingScalaCompiler.java      | 105 +++
 .../api/internal/tasks/scala/ScalaCompileSpec.java |   9 +
 .../scala/ScalaCompilerArgumentsGenerator.java     |  66 ++
 .../internal/tasks/scala/ScalaCompilerFactory.java |  68 ++
 .../tasks/scala/jdk6/ZincScalaCompiler.java        | 112 ++++
 .../api/plugins/scala/ScalaBasePlugin.groovy       |  78 ++-
 .../api/tasks/scala/IncrementalCompileOptions.java |  72 +++
 .../org/gradle/api/tasks/scala/ScalaCompile.java   | 116 +++-
 .../api/tasks/scala/ScalaCompileOptions.groovy     | 146 -----
 .../api/tasks/scala/ScalaCompileOptions.java       | 354 ++++++++++
 .../gradle/api/tasks/scala/ScalaDocOptions.groovy  |  29 +-
 .../gradle/api/tasks/scala/ScalaForkOptions.java   |  43 ++
 .../scala/NormalizingScalaCompilerTest.groovy      | 127 ++++
 .../ScalaCompilerArgumentsGeneratorTest.groovy     | 105 +++
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |  70 +-
 .../api/tasks/scala/ScalaCompileOptionsTest.groovy |  63 +-
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |   2 -
 .../api/tasks/scala/ScalaDocOptionsTest.groovy     |   5 +-
 .../org/gradle/api/tasks/scala/ScalaDocTest.java   |   2 -
 .../plugins/signing/SigningSamplesSpec.groovy      |  10 +-
 subprojects/sonar/sonar.gradle                     |   5 +
 .../plugins/sonar/SonarSmokeIntegrationTest.groovy |  89 +++
 .../SonarSmokeIntegrationTest/shared/build.gradle  |  14 +
 .../shared/src/main/java/Person.java               |   5 +
 .../gradle/api/plugins/sonar/SonarAnalyze.groovy   |   4 +-
 .../gradle/api/plugins/sonar/SonarPlugin.groovy    |  10 +-
 .../SamplesToolingApiIntegrationTest.groovy        |  14 +-
 .../tooling/ToolingApiIntegrationTest.groovy       | 147 ++++-
 .../tooling/ToolingApiRemoteIntegrationTest.groovy |  64 ++
 .../integtests/tooling/fixture/ToolingApi.groovy   |  11 +-
 .../ToolingApiCompatibilitySuiteRunner.groovy      |  13 +-
 .../tooling/fixture/ToolingApiSpecification.groovy |  16 +-
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |  13 +
 ...ildableEclipseModelFixesCrossVersionSpec.groovy |   7 +-
 .../m5/ToolingApiIdeaModelCrossVersionSpec.groovy  |   4 +-
 ...rictLongRunningOperationCrossVersionSpec.groovy |  17 +-
 .../m8/UnknownModelFeedbackCrossVersionSpec.groovy |   7 +-
 .../m9/DaemonErrorFeedbackCrossVersionSpec.groovy  |   4 +-
 .../M9JavaConfigurabilityCrossVersionSpec.groovy   |   4 +-
 ...singCommandLineArgumentsCrossVersionSpec.groovy |   4 +-
 .../DependencyMetaDataCrossVersionSpec.groovy      |   8 +-
 .../r11rc1/MigrationModelCrossVersionSpec.groovy   |  69 --
 .../r12rc1/BuildModelCrossVersionSpec.groovy       |  47 ++
 .../ProjectOutcomesModuleCrossVersionSpec.groovy   |  89 +++
 ...pportedOperationFeedbackCrossVersionSpec.groovy |  39 ++
 .../build.gradle                                   |   9 -
 .../file.txt                                       |   1 -
 .../src/main/java/Person.java                      |   3 -
 .../modelContainsAllProjects/build.gradle          |  18 -
 .../modelContainsAllProjects/settings.gradle       |  17 -
 .../modelContainsAllTestResults/build.gradle       |  21 -
 .../java/org/gradle/tooling/BuildLauncher.java     |  23 +-
 .../java/org/gradle/tooling/GradleConnector.java   |  10 +-
 .../org/gradle/tooling/LongRunningOperation.java   |  14 +-
 .../main/java/org/gradle/tooling/ModelBuilder.java |  22 +
 .../java/org/gradle/tooling/ProgressEvent.java     |   2 +
 .../java/org/gradle/tooling/ProgressListener.java  |   2 +
 .../java/org/gradle/tooling/ProjectConnection.java |   6 +
 .../java/org/gradle/tooling/ResultHandler.java     |   3 +
 .../internal/consumer/DefaultBuildLauncher.java    |  20 +-
 .../internal/consumer/DefaultModelBuilder.java     |  18 +-
 .../internal/consumer/DistributionFactory.java     |   2 +-
 .../tooling/internal/consumer/ModelProvider.java   |  35 +-
 .../internal/consumer/async/AsyncConnection.java   |   5 +-
 .../consumer/async/DefaultAsyncConnection.java     |  14 +-
 .../connection/AbstractConsumerConnection.java     |  47 ++
 .../consumer/connection/AdaptedConnection.java     |  43 +-
 .../BuildActionRunnerBackedConsumerConnection.java |  40 ++
 .../consumer/connection/ConsumerConnection.java    |   5 +-
 ...InternalConnectionBackedConsumerConnection.java |  35 +
 .../consumer/connection/LazyConnection.java        |  23 +-
 .../connection/LoggingInitializerConnection.java   |  10 +-
 .../connection/ProgressLoggingConnection.java      |  16 +-
 .../loader/CachingToolingImplementationLoader.java |   5 +-
 .../loader/DefaultToolingImplementationLoader.java |  23 +-
 .../SynchronizedToolingImplementationLoader.java   |   7 +-
 .../loader/ToolingImplementationLoader.java        |   3 +-
 .../parameters/ConsumerConnectionParameters.java   |  36 ++
 .../parameters/ConsumerOperationParameters.java    |  13 +-
 .../protocoladapter/ConsumerPropertyHandler.java   |  41 ++
 .../consumer/protocoladapter/MethodInvocation.java |  76 +++
 .../consumer/protocoladapter/MethodInvoker.java    |  21 +
 .../protocoladapter/ModelPropertyHandler.java      |  51 --
 .../protocoladapter/ProtocolToModelAdapter.java    | 246 +++++--
 .../protocoladapter/TargetTypeProvider.java        |   2 +
 .../consumer/versioning/FeatureValidator.java      |  46 --
 .../internal/consumer/versioning/ModelMapping.java |   4 +-
 .../consumer/versioning/VersionDetails.java        |  13 +-
 .../DefaultIdeaSingleEntryLibraryDependency.java   |   2 +-
 .../tooling/internal/migration/DefaultArchive.java |  34 -
 .../internal/migration/DefaultProjectOutput.java   |  81 ---
 .../internal/migration/DefaultTestResult.java      |  34 -
 .../outcomes/DefaultGradleBuildOutcome.java        |  46 ++
 .../outcomes/DefaultGradleFileBuildOutcome.java    |  42 ++
 .../internal/outcomes/DefaultProjectOutcomes.java  |  80 +++
 .../internal/protocol/BuildActionRunner.java       |  33 +
 .../tooling/internal/protocol/BuildParameters.java |  28 +
 .../tooling/internal/protocol/BuildResult.java     |  29 +
 .../internal/protocol/ConfigurableConnection.java  |  26 +
 .../internal/protocol/ConnectionParameters.java    |  29 +
 .../internal/protocol/ConnectionVersion4.java      |  14 +-
 .../internal/protocol/InternalConnection.java      |   6 +-
 .../internal/protocol/InternalProjectOutcomes.java |  20 +
 .../internal/protocol/InternalProjectOutput.java   |  20 -
 .../gradle/tooling/model/ExternalDependency.java   |   4 +-
 .../gradle/tooling/model/GradleModuleVersion.java  |   4 +-
 .../main/java/org/gradle/tooling/model/Task.java   |   1 -
 .../gradle/tooling/model/internal/Exceptions.java  |   4 -
 .../tooling/model/internal/migration/Archive.java  |  26 -
 .../model/internal/migration/ProjectOutput.java    |  34 -
 .../model/internal/migration/TaskOutput.java       |  24 -
 .../model/internal/migration/TestResult.java       |  26 -
 .../internal/outcomes/GradleBuildOutcome.java      |  55 ++
 .../internal/outcomes/GradleFileBuildOutcome.java  |  53 ++
 .../model/internal/outcomes/ProjectOutcomes.java   |  33 +
 .../consumer/DefaultBuildLauncherTest.groovy       |  34 +-
 .../consumer/DefaultModelBuilderTest.groovy        |  47 +-
 .../consumer/ProtocolToModelAdapterTest.groovy     |  22 +
 .../connection/AdaptedConnectionTest.groovy        |  50 ++
 ...ActionRunnerBackedConsumerConnectionTest.groovy |  61 ++
 ...alConnectionBackedConsumerConnectionTest.groovy |  47 ++
 .../consumer/connection/LazyConnectionTest.groovy  |  54 +-
 .../ProgressLoggingConnectionTest.groovy           |  27 +-
 .../CachingToolingImplementationLoaderTest.groovy  |  20 +-
 .../DefaultToolingImplementationLoaderTest.groovy  | 116 +++-
 ...chronizedToolingImplementationLoaderTest.groovy |  14 +-
 .../internal/consumer/loader/TestConnection.java   |  36 --
 .../ProtocolToModelAdapterTest.groovy              | 149 ++++-
 subprojects/tooling-api/tooling-api.gradle         |  12 +-
 subprojects/ui/ui.gradle                           |   4 -
 .../WrapperCrossVersionIntegrationTest.groovy      |   0
 .../WrapperProjectIntegrationTest.groovy           | 180 ++++++
 .../src/main/java/org/gradle/wrapper/Download.java |  40 +-
 .../java/org/gradle/wrapper/GradleWrapperMain.java |  31 +-
 .../groovy/org/gradle/wrapper/DownloadTest.groovy  |   7 +-
 .../gradle/wrapper/GradleWrapperMainTest.groovy    |  27 +
 subprojects/wrapper/wrapper.gradle                 |  17 +-
 1866 files changed, 71441 insertions(+), 28711 deletions(-)

diff --git a/build.gradle b/build.gradle
index a157357..504fade 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,168 +15,119 @@
  */
 
 import org.gradle.build.Install
+import org.gradle.build.BuildTypes
 import org.gradle.build.TestReportAggregator
 
-/**
- * For building Gradle you usually don't need to specify any properties. Only certain functionality of the Gradle requires
- * setting certain properties. Those properties can be set in the gradle.properties file in the the gradle user home. The
- * following properties can be set:
- *
- * Uploading distributions to Gradle's release and snapshot repository at codehaus: artifactoryUserName, artifactoryUserPassword
- */
-
 defaultTasks 'assemble'
 apply plugin: 'java-base'
 archivesBaseName = 'gradle'
-apply from: "gradle/versioning.gradle"
 
-ext {
-    versions = [
-        commons_io: 'commons-io:commons-io:1.4'
-    ]
-    libraries = [
-        ant: dependencies.module('org.apache.ant:ant:1.8.4') {
-            dependency 'org.apache.ant:ant-launcher:1.8.4 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",
-    ]
+extensions.buildTypes = new BuildTypes(project)
 
-}
+buildTypes {
+    // The minimum to be run before check-in
+    preCommitBuild "doc:checkstyleApi", "codeQuality", "classes", "test", noDistTests: true
+    quickCheck "doc:checkstyleApi", "codeQuality", "classes", "test", noDistTests: true
 
-// Logging
-libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.6 at jar'
-libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.6') {
-    dependency libraries.slf4j_api
-}
-libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.6') {
-    dependency libraries.slf4j_api
-}
-libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.6') {
-    dependency libraries.slf4j_api
-}
-libraries.logback_core = 'ch.qos.logback:logback-core:1.0.6 at jar'
-libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.6') {
-    dependency libraries.logback_core
-    dependency libraries.slf4j_api
-}
+    // A full (in-process) test
+    developerBuild "check"
 
-// 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
-}
+    // Used by the first phase of the build pipeline
+    quickTest "test", "integTest", noDistTests: true
 
-libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.2.1') {
-    dependency "org.apache.httpcomponents:httpcore:4.2.1 at jar"
-    dependency libraries.jcl_to_slf4j
-    dependency "commons-codec:commons-codec:1.6 at jar"
-    dependency "org.samba.jcifs:jcifs:1.3.17"
-}
+    // Used for builds to test the code on certain platforms
+    platformTest "test", "forkingIntegTest", noDistTests: true, useIncomingDistributions: true
+
+    // Tests using the daemon mode
+    daemonTest "daemonIntegTest", noDistTests: true, useIncomingDistributions: true
+
+    // Run the integration tests using the parallel executer
+    parallelTest "parallelIntegTest", noDistTests: true, useIncomingDistributions: true
 
-libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-tasks:2.1.3") {
-    libraries.ant
+    // Run the performance tests
+    performanceTest "performance:integTest", useIncomingDistributions: true
+
+    // Used for cross version tests on CI
+    crossVersionTest "forkingIntegTest", crossVersionTestsOnly: "", testAllVersions: "", noDistTests: true, useIncomingDistributions: true
+
+    // Used to build production distros and smoke test them
+    packageBuild "verifyIsProductionBuildEnvironment", "clean", "buildDists", "distributions:integTest"
+
+    // Used to build production distros and smoke test them
+    promotionBuild "verifyIsProductionBuildEnvironment", "clean", "buildDists", "distributions:integTest", "uploadArchives"
 }
 
-libraries += [
-        ant_junit: 'org.apache.ant:ant-junit:1.8.4 at jar',
-        ant_antlr: 'org.apache.ant:ant-antlr:1.8.4 at jar',
-        antlr: 'antlr:antlr:2.7.7 at jar',
-        dom4j: 'dom4j:dom4j:1.6.1 at jar',
-        guava: 'com.google.guava:guava:11.0.2 at jar',
-        jsr305: 'com.google.code.findbugs:jsr305:1.3.9',
-        groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
-        jaxen: 'jaxen:jaxen:1.1 at jar',
-        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.maven3_settings_builder = dependencies.module("org.apache.maven:maven-settings-builder:3.0.4") {
-    dependency "org.apache.maven:maven-settings:3.0.4 at jar"
-    dependency "org.codehaus.plexus:plexus-utils:2.0.6 at jar"
-    dependency "org.codehaus.plexus:plexus-interpolation:1.14 at jar"
-    dependency "org.codehaus.plexus:plexus-component-annotations:1.5.5 at jar"
-    dependency "org.sonatype.plexus:plexus-cipher:1.4 at jar"
-    dependency "org.sonatype.plexus:plexus-sec-dispatcher:1.3 at jar"
+ext {
+    jvm = org.gradle.internal.jvm.Jvm.current()
+    javaVersion = JavaVersion.current()
+    isCiServer = System.getenv().containsKey("TEAMCITY_VERSION")
+    isWindows = org.gradle.internal.os.OperatingSystem.current().windows
+
+    if (project.hasProperty("maxParallelForks")) {
+        project.maxParallelForks = Integer.valueOf(project.maxParallelForks, 10)
+    } else {
+        ext.maxParallelForks = Math.max(2, (int) (Runtime.runtime.availableProcessors() / 2))
+    }
+
+    if (project.hasProperty("useIncomingDistributions")) {
+        project.useIncomingDistributions = true
+    } else {
+        ext.useIncomingDistributions = false
+    }
+
+    internalProjects = subprojects.findAll { it.name.startsWith("internal") || it.name in ["integTest", "distributions"] }
+    groovyProjects = subprojects.findAll { !(it.name in ["docs"]) }
+    publicGroovyProjects = groovyProjects - internalProjects
+    publishedProjects = [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices'), project(':messaging')]
+    pluginProjects = [
+        'plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven',
+        'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear', 'javascript', 'buildComparison',
+        'diagnostics', 'reporting', 'publish', 'ivy'
+    ].collect {
+        project(it)
+    }
 }
 
-libraries.spock = ['org.spockframework:spock-core:0.6-groovy-1.8 at jar',
-        libraries.groovy,
-        'org.objenesis:objenesis:1.2',
-        'cglib:cglib-nodep:2.2']
-libraries.jmock = ['org.jmock:jmock:2.5.1',
-        'org.hamcrest:hamcrest-core:1.1',
-        'org.hamcrest:hamcrest-library:1.1',
-        dependencies.create('org.jmock:jmock-junit4:2.5.1') { exclude group: 'junit', module: 'junit-dep' }, //junit-dep pulls old definitions of core junit types.
-        'org.jmock:jmock-legacy:2.5.1',
-        'org.objenesis:objenesis:1.2',
-        'cglib:cglib-nodep:2.2']
+apply from: "gradle/buildReceipt.gradle"
+apply from: "gradle/incomingDistributions.gradle"
+apply from: "gradle/versioning.gradle"
+apply from: "gradle/dependencies.gradle"
+apply from: "gradle/wrapper.gradle"
+apply from: "gradle/idea.gradle"
+apply from: "gradle/eclipse.gradle"
+apply from: "gradle/classycle.gradle"
+apply from: "gradle/noDependencyResolutionDuringConfiguration.gradle"
 
 allprojects {
     group = 'org.gradle'
 
-    plugins.withType(JavaPlugin) {
-        sourceCompatibility = 1.5
-        targetCompatibility = 1.5
-    }
-
     repositories {
         maven { url 'http://repo.gradle.org/gradle/libs' }
-        maven { url 'http://repository.codehaus.org/' }
     }
+}
 
-    version = this.version
-
-    apply from: "$rootDir/gradle/conventions-dsl.gradle"
+subprojects {
+    version = rootProject.version
 
-    ext {
-        isDevBuild = {
-            gradle.taskGraph.hasTask(developerBuild)
-        }
-
-        isCIBuild = {
-            gradle.taskGraph.hasTask(ciBuild)
-        }
-
-        isCommitBuild = {
-            gradle.taskGraph.hasTask(commitBuild)
-        }
+    if (project in groovyProjects) {
+        apply from: "$rootDir/gradle/groovyProject.gradle"
+        apply from: "$rootDir/gradle/testWithUnknownOS.gradle"
+        check.dependsOn ":docs:checkstyleApi"
+        check.dependsOn "codeQuality"
     }
-}
-
-configure(groovyProjects()) {
-    apply from: "$rootDir/gradle/groovyProject.gradle"
-}
 
-configure(publishedProjects()) {
-    apply from: "$rootDir/gradle/publish.gradle"
-}
+    if (project in publishedProjects) {
+        apply from: "$rootDir/gradle/publish.gradle"
+    }
 
-allprojects {
     apply from: "$rootDir/gradle/codeQuality.gradle"
-    apply from: "$rootDir/gradle/testWithUnknownOS.gradle"
-}
 
-apply from: "gradle/idea.gradle"
-apply from: "gradle/eclipse.gradle"
+    if (isCiServer) {
+        reporting.baseDir "$rootProject.reporting.baseDir/${path.replaceFirst(':', '').replaceAll(':', '.')}"
+    }
+}
 
 configurations {
-    dists
     runtime {
         visible = false
     }
@@ -191,304 +142,49 @@ configurations {
 
 dependencies {
     runtime project(':launcher')
-    plugins pluginProjects()
+    plugins pluginProjects
     plugins project(':coreImpl')
 }
 
-evaluationDependsOn(':docs')
-evaluationDependsOn(':integTest')
-
-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)
-
-task aggregateTestReports(type: TestReportAggregator) {
-    testReportDir = reporting.file("tests")
-    testResultsDir = file("${buildDir}/test-results")
-    projects = subprojects
-}
-
-task determineCommitId {
-    ext.commitId = null
-
-    doLast {
-        def strategies = []
-
-        def env = System.getenv()
-
-        // Builds of Gradle happening on the CI server
-        strategies << {
-            env["BUILD_VCS_NUMBER"]
-        }
-
-        // For the discovery builds, this points to the Gradle revision
-        strategies << {
-            env.find { it.key.startsWith("BUILD_VCS_NUMBER_Gradle_Master") }?.value
-        }
-
-        // If it's a checkout, ask Git for it
-        strategies << {
-            if (file(".git").exists()) {
-                def baos = new ByteArrayOutputStream()
-                exec {
-                    executable "git"
-                    args "log", "-1", "--format=%H"
-                    standardOutput = baos
-                }
-                new String(baos.toByteArray(), "utf8").trim()
-            } else {
-                null
-            }
-        }
-
-        for (strategy in strategies) {
-            commitId = strategy()
-            if (commitId) {
-                break
-            }
-        }
-        if (!commitId) {
-            throw new InvalidUserDataException("Could not determine commit id")
-        }
-    }
-}
-
-task createBuildReceipt(dependsOn: determineCommitId) {
-    ext.receiptFile = file("$buildDir/build-receipt.properties")
-    outputs.file receiptFile
-    outputs.upToDateWhen { false }
-    doLast {
-        def data = [
-            commitId:  determineCommitId.commitId,
-            versionNumber: version,
-            buildTimestamp: buildTimestamp,
-            username: System.properties["user.name"],
-            hostname: InetAddress.localHost.hostName,
-            javaVersion: System.properties["java.version"],
-            osName: System.properties["os.name"],
-            osVersion: System.properties["os.version"]
-        ]
-
-        receiptFile.parentFile.mkdirs()
-
-        // We write this out ourself instead of using the properties class to avoid the
-        // annoying timestamp that insists on placing in there, that throws out incremental.
-        def content = data.entrySet().collect { "$it.key=$it.value" }.sort().join("\n")
-        receiptFile.setText(content, "ISO-8859-1")
-    }
-}
-
-ext.zipRootFolder = "$archivesBaseName-${-> version}"
-
-ext.binDistImage = copySpec {
-    from('src/toplevel') {
-        exclude 'media/**'
-        expand(version: version)
-    }
-    from('src/toplevel') {
-        include 'media/**'
-    }
-    from project(':docs').outputs.distDocs
-    into('bin') {
-        from { project(':launcher').startScripts.outputs.files }
-        fileMode = 0755
-    }
-    into('lib') {
-        from configurations.runtime
-        into('plugins') {
-            from configurations.plugins - configurations.runtime
-        }
-    }
-}
-
-ext.allDistImage = copySpec {
-    with binDistImage
-    into('src') {
-        from groovyProjects().collect {project -> project.sourceSets.main.allSource }
-    }
-    into('docs') {
-        from project(':docs').outputs.docs
-    }
-    into('samples') {
-        from project(':docs').outputs.samples
-    }
-}
-
-task allZip(type: Zip) {
-    classifier = 'all'
-    into(zipRootFolder) {
-        with allDistImage
-    }
-}
-
-task binZip(type: Zip) {
-    classifier = 'bin'
-    into(zipRootFolder) {
-        with binDistImage
-    }
+task verifyIsProductionBuildEnvironment << {
+    assert javaVersion.java7 : "Must use a Java 7 compatible JVM to perform this build. Current JVM is ${jvm}"
+    def systemCharset = java.nio.charset.Charset.defaultCharset().name()
+    assert systemCharset == "UTF-8" : "Platform encoding must be UTF-8. Is currently $systemCharset. Set -Dfile.encoding=UTF-8."
 }
 
-task srcZip(type: Zip) {
-    classifier = 'src'
-    into(zipRootFolder) {
-        from('gradlew') {
-            fileMode = 0755
+task waitForDaemonsToDie {
+    if (!project.hasProperty("noWaitForDaemonsToDie")) {
+        if (isWindows && isCiServer) {
+            gradle.startParameter.taskNames.add(0, it.path)
         }
-        from(projectDir) {
-            def spec = delegate
-            ['buildSrc', 'subprojects/*'].each {
-                spec.include "$it/*.gradle"
-                spec.include "$it/src/"
-            }
-            include 'config/'
-            include 'gradle/'
-            include 'src/'
-            include '*.gradle'
-            include 'wrapper/'
-            include 'gradlew.bat'
+        doLast {
+            def mins = 2
+            println "I'm waiting for $mins mins so that existing daemons can die with honour. It's a workaround until we fix it properly."
+            sleep mins * 60 * 1000
         }
     }
 }
 
-task outputsZip(type: Zip) {
-    archiveName "outputs.zip"
-    from(createBuildReceipt)
-    ["all", "bin", "src"].each { from(tasks["${it}Zip"]) }
-}
-
-artifacts {
-    dists allZip, binZip, srcZip
-}
-
-task intTestImage(type: Sync) {
-    dependsOn "launcher:startScripts"
-    with binDistImage
-    ext.integTestGradleHome = file("$buildDir/integ test")
-    into integTestGradleHome
-    doLast { task ->
-        ant.chmod(dir: "$integTestGradleHome/bin", perm: "ugo+rx", includes: "**/*")
-    }
+task aggregateTestReports(type: TestReportAggregator) {
+    testReportDir = reporting.file("tests")
+    testResultsDir = file("${buildDir}/test-results")
+    projects = subprojects
 }
 
-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))
-}
+evaluationDependsOn ":distributions"
 
 task install(type: Install) {
     description = 'Installs the minimal distribution into directory $gradle_installPath'
     group = 'build'
-    dependsOn binZip.taskDependencies
-    with binDistImage
+    with project(":distributions").binDistImage
     installDirPropertyName = 'gradle_installPath'
 }
 
 task installAll(type: Install) {
     description = 'Installs the full distribution into directory $gradle_installPath'
     group = 'build'
-    dependsOn allZip.taskDependencies
-    with allDistImage
+    with project(":distributions").allDistImage
     installDirPropertyName = 'gradle_installPath'
 }
 
-task testedDists(dependsOn: [check]) {
-    outputs.files configurations.dists.allArtifacts.files
-}
-
-gradle.taskGraph.whenReady {graph ->
-    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'
-    dependsOn testedDists
-}
-
-task ciBuild {
-    description = 'Full build performed by the CI server'
-    dependsOn clean, testedDists
-}
-
-task commitBuild {
-    description = 'Commit build performed by the CI server'
-    dependsOn testedDists
-}
-
-task verifyIsProductionBuildEnvironment << {
-    assert Jvm.current().isJava7() : "Must use a Java 7 compatible JVM to perform this build. Current JVM is ${Jvm.current()}"
-    def systemCharset = java.nio.charset.Charset.defaultCharset().name()
-    assert systemCharset == "UTF-8" : "Platform encoding must be UTF-8. Is currently $systemCharset. Set -Dfile.encoding=UTF-8."
-}
-
-gradle.taskGraph.whenReady {graph ->
-    if (graph.hasTask(ciBuild)) {
-        subprojects { reporting.baseDir "$rootProject.reporting.baseDir/${path.replaceFirst(':', '').replaceAll(':', '.')}" }
-    }
-}
-
-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"]) }
-}
-
-def publishedProjects() {
-    [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices'), project(':messaging')]
-}
-
-def pluginProjects() {
-    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear', 'javascript'].collect {
-        project(it)
-    }
-}
-
+apply from: "gradle/intTestImage.gradle"
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index d44058d..2d466be 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -35,9 +35,12 @@ dependencies {
 
     compile "org.pegdown:pegdown:1.1.0"
     compile "org.jsoup:jsoup:1.6.3"
-    compile "com.googlecode.jarjar:jarjar:1.3"
-}
 
+    //below dependency was deployed to the repo.gradle.org
+    //it's built from sources at: https://github.com/szczepiq/jarjar
+    //if code changes are needed in this library we need to figure out a better way of distributing them.
+    compile "org.gradle.jarjar:jarjar:1.2.1"
+}
 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/BuildTypes.groovy b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
new file mode 100644
index 0000000..4490268
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
@@ -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.build
+
+import org.gradle.api.Project
+import org.gradle.api.GradleException
+
+class BuildTypes {
+
+    private final Project project
+    private final activeNames = []
+
+    BuildTypes(Project project) {
+        this.project = project
+    }
+
+    List<String> getActive() {
+        new LinkedList(activeNames)
+    }
+
+    boolean isActive(String name) {
+        name in activeNames
+    }
+
+    def methodMissing(String name, args) {
+        args = args.toList()
+        def properties = [:]
+        if (args.first() instanceof Map) {
+            properties.putAll(args.remove(0))
+        }
+        def tasks = args*.toString()
+
+        register(name, tasks, properties)
+    }
+
+    private register(name, tasks, projectProperties) {
+        project.task(name) {
+            group = "Build Type"
+            def abbreviation = name[0] + name[1..-1].replaceAll("[a-z]", "")
+            def taskNames = project.gradle.startParameter.taskNames
+
+            def usedName = taskNames.find { it in [name, abbreviation] }
+            if (usedName) {
+                activeNames << name
+                def index = taskNames.indexOf(usedName)
+                taskNames.remove((int)index)
+                tasks.reverse().each {
+                    taskNames.add(index, it)
+                }
+                projectProperties.each { k, v ->
+                    if (!project.hasProperty(k)) {
+                        project.ext."$k" = null
+                    }
+                    project.properties."$k" = v
+                }
+            }
+
+            doFirst {
+                throw new GradleException("'$name' is a build type and has to be invoked directly, and its name can only be abbreviated to '$abbreviation'.")
+            }
+         }
+    }
+
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy b/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy
index afe9b10..7bb4f75 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy
@@ -14,34 +14,33 @@
  * limitations under the License.
  */
 
+
+
 package org.gradle.build
 
 import com.tonicsystems.jarjar.Main as JarJarMain
-import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.internal.file.TemporaryFileProvider
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
+import org.gradle.api.*
+import org.gradle.api.file.*
+import org.gradle.api.tasks.*
 
 class JarJar extends DefaultTask {
-    @InputFile File inputFile
-    @OutputFile File outputFile
+    @InputFiles FileCollection inputJars
+    @OutputDirectory File outputDir
 
     @Input def rules = [:]
     @Input def keeps = []
 
     @TaskAction
     void nativeJarJar() {
-        try {
-            TemporaryFileProvider tmpFileProvider = getServices().get(TemporaryFileProvider);
-            File tempRuleFile = tmpFileProvider.createTemporaryFile("jarjar", "rule")
-            writeRuleFile(tempRuleFile)
+        File tempRuleFile = new File(getTemporaryDir(), "jarjar.rules.txt")
+        writeRuleFile(tempRuleFile)
+
+        if (inputJars.empty) {
+            throw new GradleException("Unable to execute JarJar task because there are no input jars.");
+        }
 
-            JarJarMain.main("process", tempRuleFile.absolutePath, inputFile.absolutePath, outputFile.absolutePath)
-        } catch (IOException e) {
-            throw new GradleException("Unable to execute JarJar task", e);
+        for(file in inputJars) {
+            JarJarMain.main("process", tempRuleFile.absolutePath, file.absolutePath, new File(outputDir, "jarjar-$file.name").absolutePath)
         }
     }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
index 7c43704..0db43f9 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
@@ -69,7 +69,7 @@ class BuildableDOMCategory {
         parent.insertBefore(n, sibling)
     }
 
-    public static void addAfter(Element sibling, Closure cl) {
+    public static Object addAfter(Element sibling, Closure cl) {
         DomBuilder builder = new DomBuilder(sibling.ownerDocument, null)
         cl.delegate = builder
         cl.call()
@@ -78,5 +78,6 @@ class BuildableDOMCategory {
         builder.elements.each { element ->
             parent.insertBefore(element, next)
         }
+        return builder.elements.size() == 1 ? builder.elements[0] : builder.elements
     }
 }
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 cb0421b..02dad83 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
@@ -20,8 +20,8 @@ package org.gradle.build.docs
 import groovy.xml.dom.DOMCategory
 import org.gradle.api.DefaultTask
 import org.gradle.api.InvalidUserDataException
-import org.gradle.build.docs.dsl.ClassLinkMetaData
-import org.gradle.build.docs.dsl.LinkMetaData
+import org.gradle.build.docs.dsl.links.ClassLinkMetaData
+import org.gradle.build.docs.dsl.links.LinkMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
 import org.w3c.dom.Document
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
deleted file mode 100644
index 38a5989..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassLinkMetaData.java
+++ /dev/null
@@ -1,164 +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;
-
-import org.gradle.build.docs.dsl.model.ClassMetaData;
-import org.gradle.build.docs.dsl.model.MethodMetaData;
-import org.gradle.build.docs.model.Attachable;
-import org.gradle.build.docs.model.ClassMetaDataRepository;
-import org.gradle.util.GUtil;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-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, style);
-        }
-    }
-
-    public LinkMetaData getClassLink() {
-        return new LinkMetaData(style, simpleName, null);
-    }
-
-    public String getPackageName() {
-        return packageName;
-    }
-
-    public LinkMetaData getMethod(String method) {
-        MethodLinkMetaData methodMetaData = findMethod(method);
-        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) {
-        MethodLinkMetaData metaData = methods.get(method);
-        if (metaData != null) {
-            return metaData;
-        }
-
-        List<MethodLinkMetaData> candidates = new ArrayList<MethodLinkMetaData>();
-        for (MethodLinkMetaData methodLinkMetaData : methods.values()) {
-            if (methodLinkMetaData.name.equals(method)) {
-                candidates.add(methodLinkMetaData);
-            }
-        }
-        if (candidates.isEmpty()) {
-            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) {
-            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);
-    }
-
-    public LinkMetaData.Style getStyle() {
-        return style;
-    }
-
-    public void setStyle(LinkMetaData.Style style) {
-        this.style = 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 {
-        final String name;
-        final String signature;
-        final LinkMetaData.Style style;
-
-        private MethodLinkMetaData(String name, String signature, LinkMetaData.Style style) {
-            this.name = name;
-            this.signature = signature;
-            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
deleted file mode 100644
index eedb6ce..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
+++ /dev/null
@@ -1,144 +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
-
-import groovyjarjarantlr.collections.AST
-import org.codehaus.groovy.antlr.AntlrASTProcessor
-import org.codehaus.groovy.antlr.SourceBuffer
-import org.codehaus.groovy.antlr.UnicodeEscapingReader
-import org.codehaus.groovy.antlr.java.Java2GroovyConverter
-import org.codehaus.groovy.antlr.java.JavaLexer
-import org.codehaus.groovy.antlr.java.JavaRecognizer
-import org.codehaus.groovy.antlr.parser.GroovyLexer
-import org.codehaus.groovy.antlr.parser.GroovyRecognizer
-import org.codehaus.groovy.antlr.treewalker.PreOrderTraversal
-import org.codehaus.groovy.antlr.treewalker.SourceCodeTraversal
-import org.codehaus.groovy.antlr.treewalker.Visitor
-import org.gradle.api.Action
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.SourceTask
-import org.gradle.api.tasks.TaskAction
-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.build.docs.model.SimpleClassMetaDataRepository
-import org.gradle.util.Clock
-import org.gradle.build.docs.DocGenerationException
-import org.gradle.api.Transformer
-
-/**
- * Extracts meta-data from the Groovy and Java source files which make up the Gradle DSL. Persists the meta-data to a file
- * for later use in generating the docbook source for the DSL, such as by {@link org.gradle.build.docs.dsl.docbook.AssembleDslDocTask}.
- */
-class ExtractDslMetaDataTask extends SourceTask {
-    @OutputFile
-    def File destFile
-
-    @TaskAction
-    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 ->
-            parse(f, repository)
-            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 ->
-            fullyQualifyAllTypeNames(metaData, resolver)
-        }
-        repository.store(destFile)
-
-        println "Parsed $counter classes in ${clock.time}"
-    }
-
-    def parse(File sourceFile, ClassMetaDataRepository<ClassMetaData> repository) {
-        try {
-            sourceFile.withReader { reader ->
-                if (sourceFile.name.endsWith('.java')) {
-                    parseJava(sourceFile, reader, repository)
-                } else {
-                    parseGroovy(sourceFile, reader, repository)
-                }
-            }
-        } catch (Exception e) {
-            throw new DocGenerationException("Could not parse '$sourceFile'.", e)
-        }
-    }
-
-    def parseJava(File sourceFile, Reader input, ClassMetaDataRepository<ClassMetaData> repository) {
-        SourceBuffer sourceBuffer = new SourceBuffer();
-        UnicodeEscapingReader unicodeReader = new UnicodeEscapingReader(input, sourceBuffer);
-        JavaLexer lexer = new JavaLexer(unicodeReader);
-        unicodeReader.setLexer(lexer);
-        JavaRecognizer parser = JavaRecognizer.make(lexer);
-        parser.setSourceBuffer(sourceBuffer);
-        String[] tokenNames = parser.getTokenNames();
-
-        parser.compilationUnit();
-        AST ast = parser.getAST();
-
-        // modify the Java AST into a Groovy AST (just token types)
-        Visitor java2groovyConverter = new Java2GroovyConverter(tokenNames);
-        AntlrASTProcessor java2groovyTraverser = new PreOrderTraversal(java2groovyConverter);
-        java2groovyTraverser.process(ast);
-
-        def visitor = new SourceMetaDataVisitor(sourceBuffer, repository, false)
-        AntlrASTProcessor traverser = new SourceCodeTraversal(visitor);
-        traverser.process(ast);
-        visitor.complete()
-    }
-
-    def parseGroovy(File sourceFile, Reader input, ClassMetaDataRepository<ClassMetaData> repository) {
-        SourceBuffer sourceBuffer = new SourceBuffer();
-        UnicodeEscapingReader unicodeReader = new UnicodeEscapingReader(input, sourceBuffer);
-        GroovyLexer lexer = new GroovyLexer(unicodeReader);
-        unicodeReader.setLexer(lexer);
-        GroovyRecognizer parser = GroovyRecognizer.make(lexer);
-        parser.setSourceBuffer(sourceBuffer);
-
-        parser.compilationUnit();
-        AST ast = parser.getAST();
-
-        def visitor = new SourceMetaDataVisitor(sourceBuffer, repository, true)
-        AntlrASTProcessor traverser = new SourceCodeTraversal(visitor);
-        traverser.process(ast);
-        visitor.complete()
-    }
-
-    def fullyQualifyAllTypeNames(ClassMetaData classMetaData, TypeNameResolver resolver) {
-        try {
-            classMetaData.resolveTypes(new Transformer<String, String>(){
-                String transform(String i) {
-                    return resolver.resolve(i, classMetaData)
-                }
-            })
-            classMetaData.visitTypes(new Action<TypeMetaData>() {
-                void execute(TypeMetaData t) {
-                    resolver.resolve(t, classMetaData)
-                }
-            })
-        } catch (Exception e) {
-            throw new RuntimeException("Could not resolve types in class '$classMetaData.className'.", e)
-        }
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index de27580..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/LinkMetaData.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.build.docs.dsl;
-
-import java.io.Serializable;
-
-public class LinkMetaData implements Serializable {
-    public enum Style { Javadoc, Groovydoc, Dsldoc }
-
-    private final Style style;
-    private final String displayName;
-    private final String urlFragment;
-
-    public LinkMetaData(Style style, String displayName, String urlFragment) {
-        this.style = style;
-        this.displayName = displayName;
-        this.urlFragment = urlFragment;
-    }
-
-    public Style getStyle() {
-        return style;
-    }
-
-    public String getDisplayName() {
-        return displayName;
-    }
-
-    public String getUrlFragment() {
-        return urlFragment;
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java
deleted file mode 100644
index d83da52..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java
+++ /dev/null
@@ -1,496 +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;
-
-import groovyjarjarantlr.collections.AST;
-import org.apache.commons.lang.StringUtils;
-import org.codehaus.groovy.antlr.GroovySourceAST;
-import org.codehaus.groovy.antlr.LineColumn;
-import org.codehaus.groovy.antlr.SourceBuffer;
-import org.codehaus.groovy.antlr.treewalker.VisitorAdapter;
-import org.gradle.build.docs.dsl.model.*;
-import org.gradle.build.docs.model.ClassMetaDataRepository;
-
-import java.lang.reflect.Modifier;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.*;
-
-public class SourceMetaDataVisitor extends VisitorAdapter {
-    private static final Pattern PREV_JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/");
-    private static final Pattern GETTER_METHOD_NAME = Pattern.compile("(get|is)(.+)");
-    private static final Pattern SETTER_METHOD_NAME = Pattern.compile("set(.+)");
-    private final SourceBuffer sourceBuffer;
-    private final LinkedList<GroovySourceAST> parseStack = new LinkedList<GroovySourceAST>();
-    private final List<String> imports = new ArrayList<String>();
-    private final ClassMetaDataRepository<ClassMetaData> repository;
-    private final List<ClassMetaData> allClasses = new ArrayList<ClassMetaData>();
-    private final LinkedList<ClassMetaData> classStack = new LinkedList<ClassMetaData>();
-    private final Map<GroovySourceAST, ClassMetaData> typeTokens = new HashMap<GroovySourceAST, ClassMetaData>();
-    private final boolean groovy;
-    private String packageName;
-    private LineColumn lastLineCol;
-
-    SourceMetaDataVisitor(SourceBuffer sourceBuffer, ClassMetaDataRepository<ClassMetaData> repository,
-                          boolean isGroovy) {
-        this.sourceBuffer = sourceBuffer;
-        this.repository = repository;
-        groovy = isGroovy;
-        lastLineCol = new LineColumn(1, 1);
-    }
-
-    public void complete() {
-        for (String anImport : imports) {
-            for (ClassMetaData classMetaData : allClasses) {
-                classMetaData.addImport(anImport);
-            }
-        }
-    }
-
-    @Override
-    public void visitPackageDef(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            packageName = extractName(t);
-        }
-    }
-
-    @Override
-    public void visitImport(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            imports.add(extractName(t));
-        }
-    }
-
-    @Override
-    public void visitClassDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
-    }
-
-    @Override
-    public void visitInterfaceDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, true);
-    }
-
-    @Override
-    public void visitEnumDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
-    }
-
-    @Override
-    public void visitAnnotationDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
-    }
-
-    private void visitTypeDef(GroovySourceAST t, int visit, boolean isInterface) {
-        if (visit == OPENING_VISIT) {
-            ClassMetaData outerClass = getCurrentClass();
-            String baseName = extractIdent(t);
-            String className = outerClass != null ? outerClass.getClassName() + '.' + baseName
-                    : packageName + '.' + baseName;
-            String comment = getJavaDocCommentsBeforeNode(t);
-            ClassMetaData currentClass = new ClassMetaData(className, packageName, isInterface, groovy, comment);
-            if (outerClass != null) {
-                outerClass.addInnerClassName(className);
-                currentClass.setOuterClassName(outerClass.getClassName());
-            }
-            findAnnotations(t, currentClass);
-            classStack.addFirst(currentClass);
-            allClasses.add(currentClass);
-            typeTokens.put(t, currentClass);
-            repository.put(className, currentClass);
-        }
-    }
-
-    private ClassMetaData getCurrentClass() {
-        return classStack.isEmpty() ? null : classStack.getFirst();
-    }
-
-    @Override
-    public void visitExtendsClause(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            ClassMetaData currentClass = getCurrentClass();
-            for (
-                    GroovySourceAST child = (GroovySourceAST) t.getFirstChild(); child != null;
-                    child = (GroovySourceAST) child.getNextSibling()) {
-                if (!currentClass.isInterface()) {
-                    currentClass.setSuperClassName(extractName(child));
-                } else {
-                    currentClass.addInterfaceName(extractName(child));
-                }
-            }
-        }
-    }
-
-    @Override
-    public void visitImplementsClause(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            ClassMetaData currentClass = getCurrentClass();
-            for (
-                    GroovySourceAST child = (GroovySourceAST) t.getFirstChild(); child != null;
-                    child = (GroovySourceAST) child.getNextSibling()) {
-                currentClass.addInterfaceName(extractName(child));
-            }
-        }
-    }
-
-    @Override
-    public void visitMethodDef(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            maybeAddMethod(t);
-            skipJavaDocComment(t);
-        }
-    }
-
-    private void maybeAddMethod(GroovySourceAST t) {
-        String name = extractName(t);
-        if (!groovy && name.equals(getCurrentClass().getSimpleName())) {
-            // A constructor. The java grammar treats a constructor as a method, the groovy grammar does not.
-            return;
-        }
-
-        ASTIterator children = new ASTIterator(t);
-        if (groovy) {
-            children.skip(TYPE_PARAMETERS);
-            children.skip(MODIFIERS);
-        } else {
-            children.skip(MODIFIERS);
-            children.skip(TYPE_PARAMETERS);
-        }
-
-        String rawCommentText = getJavaDocCommentsBeforeNode(t);
-        TypeMetaData returnType = extractTypeName(children.current);
-        MethodMetaData method = getCurrentClass().addMethod(name, returnType, rawCommentText);
-
-        findAnnotations(t, method);
-        extractParameters(t, method);
-
-        Matcher matcher = GETTER_METHOD_NAME.matcher(name);
-        if (matcher.matches()) {
-            int startName = matcher.start(2);
-            String propName = name.substring(startName, startName + 1).toLowerCase() + name.substring(startName + 1);
-            PropertyMetaData property = getCurrentClass().addReadableProperty(propName, returnType, rawCommentText, method);
-            for (String annotation : method.getAnnotationTypeNames()) {
-                property.addAnnotationTypeName(annotation);
-            }
-            return;
-        }
-
-        if (method.getParameters().size() != 1) {
-            return;
-        }
-        matcher = SETTER_METHOD_NAME.matcher(name);
-        if (matcher.matches()) {
-            int startName = matcher.start(1);
-            String propName = name.substring(startName, startName + 1).toLowerCase() + name.substring(startName + 1);
-            TypeMetaData type = method.getParameters().get(0).getType();
-            getCurrentClass().addWriteableProperty(propName, type, rawCommentText, method);
-        }
-    }
-
-    private void extractParameters(GroovySourceAST t, MethodMetaData method) {
-        GroovySourceAST paramsAst = t.childOfType(PARAMETERS);
-        for (
-                GroovySourceAST child = (GroovySourceAST) paramsAst.getFirstChild(); child != null;
-                child = (GroovySourceAST) child.getNextSibling()) {
-            assert child.getType() == PARAMETER_DEF || child.getType() == VARIABLE_PARAMETER_DEF;
-            TypeMetaData type = extractTypeName((GroovySourceAST) child.getFirstChild().getNextSibling());
-            if (child.getType() == VARIABLE_PARAMETER_DEF) {
-                type.setVarargs();
-            }
-            method.addParameter(extractIdent(child), type);
-        }
-    }
-
-    @Override
-    public void visitVariableDef(GroovySourceAST t, int visit) {
-        if (visit == OPENING_VISIT) {
-            maybeAddPropertyFromField(t);
-            skipJavaDocComment(t);
-        }
-    }
-
-    private void maybeAddPropertyFromField(GroovySourceAST t) {
-        GroovySourceAST parentNode = getParentNode();
-        boolean isField = parentNode != null && parentNode.getType() == OBJBLOCK;
-        if (!isField) {
-            return;
-        }
-
-        int modifiers = extractModifiers(t);
-        boolean isConst = getCurrentClass().isInterface() || (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
-        if (isConst) {
-            visitConst(t);
-            return;
-        }
-
-        boolean isProp = groovy && !Modifier.isStatic(modifiers) && !Modifier.isPublic(modifiers)
-                && !Modifier.isProtected(modifiers) && !Modifier.isPrivate(modifiers);
-        if (!isProp) {
-            return;
-        }
-
-        ASTIterator children = new ASTIterator(t);
-        children.skip(MODIFIERS);
-
-        String propertyName = extractIdent(t);
-        TypeMetaData propertyType = extractTypeName(children.current);
-        ClassMetaData currentClass = getCurrentClass();
-
-        MethodMetaData getterMethod = currentClass.addMethod(String.format("get%s", StringUtils.capitalize(
-                propertyName)), propertyType, "");
-        PropertyMetaData property = currentClass.addReadableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), getterMethod);
-        findAnnotations(t, property);
-        if (!Modifier.isFinal(modifiers)) {
-            MethodMetaData setterMethod = currentClass.addMethod(String.format("set%s", StringUtils.capitalize(
-                    propertyName)), TypeMetaData.VOID, "");
-            setterMethod.addParameter(propertyName, propertyType);
-            currentClass.addWriteableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), setterMethod);
-        }
-    }
-
-    private void visitConst(GroovySourceAST t) {
-        String constName = extractIdent(t);
-        GroovySourceAST assign = t.childOfType(ASSIGN);
-        String value = null;
-        if (assign != null) {
-            value = extractLiteral(assign.getFirstChild());
-        }
-        getCurrentClass().getConstants().put(constName, value);
-    }
-
-    private String extractLiteral(AST ast) {
-        switch (ast.getType()) {
-            case EXPR:
-                // The java grammar wraps initialisers in an EXPR token
-                return extractLiteral(ast.getFirstChild());
-            case NUM_INT:
-            case NUM_LONG:
-            case NUM_FLOAT:
-            case NUM_DOUBLE:
-            case NUM_BIG_INT:
-            case NUM_BIG_DECIMAL:
-            case STRING_LITERAL:
-                return ast.getText();
-        }
-        return null;
-    }
-
-    public GroovySourceAST pop() {
-        if (!parseStack.isEmpty()) {
-            GroovySourceAST ast = parseStack.removeFirst();
-            ClassMetaData classMetaData = typeTokens.remove(ast);
-            if (classMetaData != null) {
-                assert classMetaData == classStack.getFirst();
-                classStack.removeFirst();
-            }
-            return ast;
-        }
-        return null;
-    }
-
-    @Override
-    public void push(GroovySourceAST t) {
-        parseStack.addFirst(t);
-    }
-
-    private GroovySourceAST getParentNode() {
-        if (parseStack.size() > 1) {
-            return parseStack.get(1);
-        }
-        return null;
-    }
-
-    private int extractModifiers(GroovySourceAST ast) {
-        GroovySourceAST modifiers = ast.childOfType(MODIFIERS);
-        if (modifiers == null) {
-            return 0;
-        }
-        int modifierFlags = 0;
-        for (
-                GroovySourceAST child = (GroovySourceAST) modifiers.getFirstChild(); child != null;
-                child = (GroovySourceAST) child.getNextSibling()) {
-            switch (child.getType()) {
-                case LITERAL_private:
-                    modifierFlags |= Modifier.PRIVATE;
-                    break;
-                case LITERAL_protected:
-                    modifierFlags |= Modifier.PROTECTED;
-                    break;
-                case LITERAL_public:
-                    modifierFlags |= Modifier.PUBLIC;
-                    break;
-                case FINAL:
-                    modifierFlags |= Modifier.FINAL;
-                    break;
-                case LITERAL_static:
-                    modifierFlags |= Modifier.STATIC;
-                    break;
-            }
-        }
-        return modifierFlags;
-    }
-
-    private TypeMetaData extractTypeName(GroovySourceAST ast) {
-        TypeMetaData type = new TypeMetaData();
-        switch (ast.getType()) {
-            case TYPE:
-                GroovySourceAST typeName = (GroovySourceAST) ast.getFirstChild();
-                extractTypeName(typeName, type);
-                break;
-            case WILDCARD_TYPE:
-                // In the groovy grammar, the bounds are sibling of the ?, in the java grammar, they are the child
-                GroovySourceAST bounds = (GroovySourceAST) (groovy ? ast.getNextSibling() : ast.getFirstChild());
-                if (bounds == null) {
-                    type.setWildcard();
-                } else if (bounds.getType() == TYPE_UPPER_BOUNDS) {
-                    type.setUpperBounds(extractTypeName((GroovySourceAST) bounds.getFirstChild()));
-                } else if (bounds.getType() == TYPE_LOWER_BOUNDS) {
-                    type.setLowerBounds(extractTypeName((GroovySourceAST) bounds.getFirstChild()));
-                }
-                break;
-            case IDENT:
-            case DOT:
-                extractTypeName(ast, type);
-                break;
-            default:
-                throw new RuntimeException(String.format("Unexpected token in type name: %s", ast));
-        }
-
-        return type;
-    }
-
-    private void extractTypeName(GroovySourceAST ast, TypeMetaData type) {
-        if (ast == null) {
-            type.setName("java.lang.Object");
-            return;
-        }
-        switch (ast.getType()) {
-            case LITERAL_boolean:
-                type.setName("boolean");
-                return;
-            case LITERAL_byte:
-                type.setName("byte");
-                return;
-            case LITERAL_char:
-                type.setName("char");
-                return;
-            case LITERAL_double:
-                type.setName("double");
-                return;
-            case LITERAL_float:
-                type.setName("float");
-                return;
-            case LITERAL_int:
-                type.setName("int");
-                return;
-            case LITERAL_long:
-                type.setName("long");
-                return;
-            case LITERAL_void:
-                type.setName("void");
-                return;
-            case ARRAY_DECLARATOR:
-                extractTypeName((GroovySourceAST) ast.getFirstChild(), type);
-                type.addArrayDimension();
-                return;
-        }
-
-        type.setName(extractName(ast));
-        GroovySourceAST typeArgs = ast.childOfType(TYPE_ARGUMENTS);
-        if (typeArgs != null) {
-            for (
-                    GroovySourceAST child = (GroovySourceAST) typeArgs.getFirstChild(); child != null;
-                    child = (GroovySourceAST) child.getNextSibling()) {
-                assert child.getType() == TYPE_ARGUMENT;
-                type.addTypeArg(extractTypeName((GroovySourceAST) child.getFirstChild()));
-            }
-        }
-    }
-
-    private void skipJavaDocComment(GroovySourceAST t) {
-        lastLineCol = new LineColumn(t.getLine(), t.getColumn());
-    }
-
-    private String getJavaDocCommentsBeforeNode(GroovySourceAST t) {
-        String result = "";
-        LineColumn thisLineCol = new LineColumn(t.getLine(), t.getColumn());
-        String text = sourceBuffer.getSnippet(lastLineCol, thisLineCol);
-        if (text != null) {
-            Matcher m = PREV_JAVADOC_COMMENT_PATTERN.matcher(text);
-            if (m.find()) {
-                result = m.group(1);
-            }
-        }
-        lastLineCol = thisLineCol;
-        return result;
-    }
-
-    private void findAnnotations(GroovySourceAST t, AbstractLanguageElement currentElement) {
-        GroovySourceAST modifiers = t.childOfType(MODIFIERS);
-        if (modifiers != null) {
-            List<GroovySourceAST> children = modifiers.childrenOfType(ANNOTATION);
-            for (GroovySourceAST child : children) {
-                String identifier = extractIdent(child);
-                currentElement.addAnnotationTypeName(identifier);
-            }
-        }
-    }
-
-    private String extractIdent(GroovySourceAST t) {
-        return t.childOfType(IDENT).getText();
-    }
-
-    private String extractName(GroovySourceAST t) {
-        if (t.getType() == DOT) {
-            GroovySourceAST firstChild = (GroovySourceAST) t.getFirstChild();
-            GroovySourceAST secondChild = (GroovySourceAST) firstChild.getNextSibling();
-            return extractName(firstChild) + "." + extractName(secondChild);
-        }
-        if (t.getType() == IDENT) {
-            return t.getText();
-        }
-        if (t.getType() == STAR) {
-            return t.getText();
-        }
-
-        GroovySourceAST child = t.childOfType(DOT);
-        if (child != null) {
-            return extractName(child);
-        }
-        child = t.childOfType(IDENT);
-        if (child != null) {
-            return extractName(child);
-        }
-
-        throw new RuntimeException(String.format("Unexpected token in name: %s", t));
-    }
-
-    private static class ASTIterator {
-        GroovySourceAST current;
-
-        private ASTIterator(GroovySourceAST parent) {
-            this.current = (GroovySourceAST) parent.getFirstChild();
-        }
-
-        void skip(int token) {
-            if (current != null && current.getType() == token) {
-                current = (GroovySourceAST) current.getNextSibling();
-            }
-        }
-    }
-}
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
deleted file mode 100644
index 7ef8321..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/TypeNameResolver.java
+++ /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.build.docs.dsl;
-
-import org.apache.commons.lang.StringUtils;
-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.internal.UncheckedException;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Resolves partial type names into fully qualified type names.
- */
-public class TypeNameResolver {
-    private final Set<String> primitiveTypes = new HashSet<String>();
-    private final List<String> groovyImplicitImportPackages = new ArrayList<String>();
-    private final List<String> groovyImplicitTypes = new ArrayList<String>();
-    private final ClassMetaDataRepository<ClassMetaData> metaDataRepository;
-
-    public TypeNameResolver(ClassMetaDataRepository<ClassMetaData> metaDataRepository) {
-        this.metaDataRepository = metaDataRepository;
-        primitiveTypes.add("boolean");
-        primitiveTypes.add("byte");
-        primitiveTypes.add("char");
-        primitiveTypes.add("short");
-        primitiveTypes.add("int");
-        primitiveTypes.add("long");
-        primitiveTypes.add("float");
-        primitiveTypes.add("double");
-        primitiveTypes.add("void");
-        groovyImplicitImportPackages.add("java.util.");
-        groovyImplicitImportPackages.add("java.io.");
-        groovyImplicitImportPackages.add("java.net.");
-        groovyImplicitImportPackages.add("groovy.lang.");
-        groovyImplicitImportPackages.add("groovy.util.");
-        groovyImplicitTypes.add("java.math.BigDecimal");
-        groovyImplicitTypes.add("java.math.BigInteger");
-
-        // check that groovy is visible.
-        try {
-            getClass().getClassLoader().loadClass("groovy.lang.Closure");
-        } catch (ClassNotFoundException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    /**
-     * Resolves the names in the given type into fully qualified names.
-     */
-    public void resolve(final TypeMetaData type, final ClassMetaData classMetaData) {
-        type.visitTypes(new Action<TypeMetaData>() {
-            public void execute(TypeMetaData t) {
-                t.setName(resolve(t.getName(), classMetaData));
-            }
-        });
-    }
-
-    /**
-     * Resolves a source type name into a fully qualified type name.
-     */
-    public String resolve(String name, ClassMetaData classMetaData) {
-        if (primitiveTypes.contains(name)) {
-            return name;
-        }
-
-        String candidateClassName;
-        String[] innerNames = name.split("\\.");
-        ClassMetaData pos = classMetaData;
-        for (int i = 0; i < innerNames.length; i++) {
-            String innerName = innerNames[i];
-            candidateClassName = pos.getClassName() + '.' + innerName;
-            if (!pos.getInnerClassNames().contains(candidateClassName)) {
-                break;
-            }
-            if (i == innerNames.length - 1) {
-                return candidateClassName;
-            }
-            pos = metaDataRepository.get(candidateClassName);
-        }
-
-        String outerClassName = classMetaData.getOuterClassName();
-        while (outerClassName != null) {
-            if (name.equals(StringUtils.substringAfterLast(outerClassName, "."))) {
-                return outerClassName;
-            }
-            ClassMetaData outerClass = metaDataRepository.get(outerClassName);
-            candidateClassName = outerClassName + '.' + name;
-            if (outerClass.getInnerClassNames().contains(candidateClassName)) {
-                return candidateClassName;
-            }
-            outerClassName = outerClass.getOuterClassName();
-        }
-
-        if (name.contains(".")) {
-            return name;
-        }
-
-        for (String importedClass : classMetaData.getImports()) {
-            String baseName = StringUtils.substringAfterLast(importedClass, ".");
-            if (baseName.equals("*")) {
-                candidateClassName = StringUtils.substringBeforeLast(importedClass, ".") + "." + name;
-                if (metaDataRepository.find(candidateClassName) != null) {
-                    return candidateClassName;
-                }
-                if (importedClass.startsWith("java.") && isVisibleClass(candidateClassName)) {
-                    return candidateClassName;
-                }
-            } else if (name.equals(baseName)) {
-                return importedClass;
-            }
-        }
-
-        candidateClassName = classMetaData.getPackageName() + "." + name;
-        if (metaDataRepository.find(candidateClassName) != null) {
-            return candidateClassName;
-        }
-
-        candidateClassName = "java.lang." + name;
-        if (isVisibleClass(candidateClassName)) {
-            return candidateClassName;
-        }
-
-        if (classMetaData.isGroovy()) {
-            candidateClassName = "java.math." + name;
-            if (groovyImplicitTypes.contains(candidateClassName)) {
-                return candidateClassName;
-            }
-            for (String prefix : groovyImplicitImportPackages) {
-                candidateClassName = prefix + name;
-                if (isVisibleClass(candidateClassName)) {
-                    return candidateClassName;
-                }
-            }
-        }
-
-        return name;
-    }
-
-    private boolean isVisibleClass(String candidateClassName) {
-        try {
-            getClass().getClassLoader().loadClass(candidateClassName);
-            return true;
-        } catch (ClassNotFoundException e) {
-            // Ignore
-        }
-        return false;
-    }
-}
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 20cb246..78d39ea 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
@@ -25,21 +25,23 @@ import org.gradle.api.tasks.TaskAction
 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.dsl.links.ClassLinkMetaData
+import org.gradle.build.docs.dsl.links.LinkMetaData
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionMetaData
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
 import org.w3c.dom.Document
 import org.w3c.dom.Element
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
 
 /**
  * Generates the docbook source for the DSL reference guide.
  *
  * Uses the following as input:
  * <ul>
- * <li>Meta-data extracted from the source by {@link org.gradle.build.docs.dsl.ExtractDslMetaDataTask}.</li>
+ * <li>Meta-data extracted from the source by {@link org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask}.</li>
  * <li>Meta-data about the plugins, in the form of an XML file.</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>
@@ -60,7 +62,7 @@ class AssembleDslDocTask extends DefaultTask {
     @InputFile
     File pluginsMetaDataFile
     @InputDirectory
-    File classDocbookDir //TODO SF - it would be nice to do some renames, docbookTemplatesDir, destLinksFile
+    File classDocbookDir
     @OutputFile
     File destFile
     @OutputFile
@@ -89,12 +91,10 @@ class AssembleDslDocTask extends DefaultTask {
                 DslDocModel model = new DslDocModel(classDocbookDir, mainDocbookTemplate, classRepository, extensions)
                 def root = mainDocbookTemplate.documentElement
                 root.section.table.each { Element table ->
-                    mergeContent(table, model, linkRepository)
+                    mergeContent(table, model)
                 }
-                extensions.each { name, plugin ->
-                    plugin.extensionClasses.each { extension ->
-                        generateDocForType(root.ownerDocument, model, linkRepository, model.getClassDoc(extension.extensionClass))
-                    }
+                model.classes.each {
+                    generateDocForType(root.ownerDocument, model, linkRepository, it)
                 }
             }
         }
@@ -108,8 +108,14 @@ class AssembleDslDocTask extends DefaultTask {
         Map<String, ClassExtensionMetaData> extensions = [:]
         provider.root.plugin.each { Element plugin ->
             def pluginId = plugin.'@id'
+            if (!pluginId) {
+                throw new RuntimeException("No id specified for plugin: ${plugin.'@description' ?: 'unknown'}")
+            }
             plugin.extends.each { Element e ->
                 def targetClass = e.'@targetClass'
+                if (!targetClass) {
+                    throw new RuntimeException("No targetClass specified for extention provided by plugin '$pluginId'.")
+                }
                 def extension = extensions[targetClass]
                 if (!extension) {
                     extension = new ClassExtensionMetaData(targetClass)
@@ -122,6 +128,9 @@ class AssembleDslDocTask extends DefaultTask {
                 def extensionClass = e.'@extensionClass'
                 if (extensionClass) {
                     def extensionId = e.'@id'
+                    if (!extensionId) {
+                        throw new RuntimeException("No id specified for extension '$extensionClass' for plugin '$pluginId'.")
+                    }
                     extension.addExtension(pluginId, extensionId, extensionClass)
                 }
             }
@@ -129,13 +138,13 @@ class AssembleDslDocTask extends DefaultTask {
         return extensions
     }
 
-    def mergeContent(Element typeTable, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
+    def mergeContent(Element typeTable, DslDocModel model) {
         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)
+            mergeTypes(typeTable, model)
         } else if (title.matches('(?i).* blocks')) {
             mergeBlocks(typeTable, model)
         } else {
@@ -171,7 +180,7 @@ class AssembleDslDocTask extends DefaultTask {
         }
     }
 
-    def mergeTypes(Element typeTable, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
+    def mergeTypes(Element typeTable, DslDocModel model) {
         typeTable.addFirst {
             thead {
                 tr {
@@ -182,14 +191,13 @@ class AssembleDslDocTask extends DefaultTask {
         }
 
         typeTable.tr.each { Element tr ->
-            mergeType(tr, model, linkRepository)
+            mergeType(tr, model)
         }
     }
 
-    def mergeType(Element typeTr, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
+    def mergeType(Element typeTr, DslDocModel model) {
         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) }
@@ -201,7 +209,7 @@ class AssembleDslDocTask extends DefaultTask {
     def generateDocForType(Document document, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository, ClassDoc classDoc) {
         try {
             //classDoc renderer renders the content of the class and also links to properties/methods
-            new ClassDocRenderer(new LinkRenderer(document, model)).mergeContent(classDoc)
+            new ClassDocRenderer(new LinkRenderer(document, model)).mergeContent(classDoc, document.documentElement)
             def linkMetaData = linkRepository.get(classDoc.name)
             linkMetaData.style = LinkMetaData.Style.Dsldoc
             classDoc.classMethods.each { methodDoc ->
@@ -213,9 +221,8 @@ class AssembleDslDocTask extends DefaultTask {
             classDoc.classProperties.each { propertyDoc ->
                 linkMetaData.addGetterMethod(propertyDoc.name, propertyDoc.metaData.getter)
             }
-            document.documentElement << classDoc.classSection
         } catch (Exception e) {
-            throw new DocGenerationException("Failed to generate documentation for class '$className'.", e)
+            throw new DocGenerationException("Failed to generate documentation for class '$classDoc.name'.", e)
         }
     }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexer.java
index cccbb2e..9af98bf 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexer.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexer.java
@@ -85,6 +85,7 @@ class BasicJavadocLexer implements JavadocLexer {
 
             visitor.onText(text.toString());
         }
+        visitor.onEnd();
     }
 
     private void parseHtmlEntity(StringBuilder buffer) {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDetailRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDetailRenderer.java
new file mode 100644
index 0000000..33ca265
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDetailRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class BlockDetailRenderer {
+    private final GenerationListener listener;
+    private final LinkRenderer linkRenderer;
+    private final ElementWarningsRenderer warningsRenderer = new ElementWarningsRenderer();
+
+    public BlockDetailRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        this.linkRenderer = linkRenderer;
+        this.listener = listener;
+    }
+
+    public void renderTo(BlockDoc blockDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+        section.setAttribute("id", blockDoc.getId());
+        section.setAttribute("role", "detail");
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(blockDoc.getName()));
+        title.appendChild(document.createTextNode(" { }"));
+
+        warningsRenderer.renderTo(blockDoc, "script block", section);
+
+        for (Element element : blockDoc.getComment()) {
+            section.appendChild(document.importNode(element, true));
+        }
+
+        Element segmentedlist = document.createElement("segmentedlist");
+        section.appendChild(segmentedlist);
+        Element segtitle = document.createElement("segtitle");
+        segmentedlist.appendChild(segtitle);
+        segtitle.appendChild(document.createTextNode("Delegates to"));
+        Element seglistitem = document.createElement("seglistitem");
+        segmentedlist.appendChild(seglistitem);
+        Element seg = document.createElement("seg");
+        seglistitem.appendChild(seg);
+        if (blockDoc.isMultiValued()) {
+            seg.appendChild(document.createTextNode("Each "));
+            seg.appendChild(linkRenderer.link(blockDoc.getType(), listener));
+            seg.appendChild(document.createTextNode(" in "));
+            // TODO - add linkRenderer.link(property)
+            Element link = document.createElement("link");
+            seg.appendChild(link);
+            link.setAttribute("linkend", blockDoc.getBlockProperty().getId());
+            literal = document.createElement("literal");
+            link.appendChild(literal);
+            literal.appendChild(document.createTextNode(blockDoc.getBlockProperty().getName()));
+        } else {
+            seg.appendChild(linkRenderer.link(blockDoc.getType(), listener));
+            seg.appendChild(document.createTextNode(" from "));
+            // TODO - add linkRenderer.link(property)
+            Element link = document.createElement("link");
+            seg.appendChild(link);
+            link.setAttribute("linkend", blockDoc.getBlockProperty().getId());
+            literal = document.createElement("literal");
+            link.appendChild(literal);
+            literal.appendChild(document.createTextNode(blockDoc.getBlockProperty().getName()));
+
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy
deleted file mode 100644
index 267d746..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.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.build.docs.dsl.docbook
-
-import org.w3c.dom.Element
-import org.gradle.build.docs.dsl.model.TypeMetaData
-
-class BlockDoc implements DslElementDoc {
-    private final MethodDoc blockMethod
-    private final PropertyDoc blockProperty
-    private final TypeMetaData type
-    private boolean multiValued
-
-    BlockDoc(MethodDoc blockMethod, PropertyDoc blockProperty, TypeMetaData type, boolean multiValued) {
-        this.blockMethod = blockMethod
-        this.type = type
-        this.blockProperty = blockProperty
-        this.multiValued = multiValued
-    }
-
-    String getId() {
-        return blockMethod.id
-    }
-
-    String getName() {
-        return blockMethod.name
-    }
-
-    boolean isMultiValued() {
-        return multiValued
-    }
-
-    TypeMetaData getType() {
-        return type
-    }
-
-    Element getDescription() {
-        return blockMethod.description;
-    }
-
-    List<Element> getComment() {
-        return blockMethod.comment
-    }
-
-    boolean isDeprecated() {
-        return blockProperty.deprecated || blockMethod.deprecated
-    }
-
-    boolean isExperimental() {
-        return blockProperty.experimental || blockMethod.experimental
-    }
-
-    PropertyDoc getBlockProperty() {
-        return blockProperty
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockTableRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockTableRenderer.java
new file mode 100644
index 0000000..b8e0000
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockTableRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class BlockTableRenderer {
+    public void renderTo(Iterable<BlockDoc> blocks, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        // <thead>
+        //   <tr>
+        //     <td>Block</td>
+        //     <td>Description</td>
+        //   </tr>
+        // </thead>
+        Element thead = document.createElement("thead");
+        parent.appendChild(thead);
+        Element tr = document.createElement("tr");
+        thead.appendChild(tr);
+        Element td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Block"));
+        td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Description"));
+
+        for (BlockDoc blockDoc : blocks) {
+            // <tr>
+            //   <td><link linkend="$id"><literal>$name</literal></link</td>
+            //   <td>$description</td>
+            // </tr>
+            tr = document.createElement("tr");
+            parent.appendChild(tr);
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            Element link = document.createElement("link");
+            td.appendChild(link);
+            link.setAttribute("linkend", blockDoc.getId());
+            Element literal = document.createElement("literal");
+            link.appendChild(literal);
+            literal.appendChild(document.createTextNode(blockDoc.getName()));
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            if (blockDoc.isDeprecated()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Deprecated"));
+            }
+            if (blockDoc.isIncubating()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Incubating"));
+            }
+            td.appendChild(document.importNode(blockDoc.getDescription(), true));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlocksRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlocksRenderer.java
new file mode 100644
index 0000000..987786e
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlocksRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc;
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+
+public class BlocksRenderer {
+    private final BlockTableRenderer blockTableRenderer = new BlockTableRenderer();
+    private final ExtensionBlocksSummaryRenderer extensionBlocksSummaryRenderer;
+    private final BlockDetailRenderer blockDetailRenderer;
+
+    public BlocksRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        blockDetailRenderer = new BlockDetailRenderer(linkRenderer, listener);
+        extensionBlocksSummaryRenderer = new ExtensionBlocksSummaryRenderer(blockTableRenderer);
+    }
+
+    public void renderTo(ClassDoc classDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element summarySection = document.createElement("section");
+        parent.appendChild(summarySection);
+
+        Element title = document.createElement("title");
+        summarySection.appendChild(title);
+        title.appendChild(document.createTextNode("Script blocks"));
+
+        boolean hasBlocks = false;
+        Collection<BlockDoc> classBlocks = classDoc.getClassBlocks();
+        if (!classBlocks.isEmpty()) {
+            hasBlocks = true;
+
+            Element table = document.createElement("table");
+            summarySection.appendChild(table);
+
+            title = document.createElement("title");
+            table.appendChild(title);
+            title.appendChild(document.createTextNode("Script blocks - " + classDoc.getSimpleName()));
+
+            blockTableRenderer.renderTo(classBlocks, table);
+        }
+
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            hasBlocks |= !extensionDoc.getExtensionBlocks().isEmpty();
+            extensionBlocksSummaryRenderer.renderTo(extensionDoc, summarySection);
+        }
+
+        if (!hasBlocks) {
+            Element para = document.createElement("para");
+            summarySection.appendChild(para);
+            para.appendChild(document.createTextNode("No script blocks"));
+            return;
+        }
+
+
+        Element detailsSection = document.createElement("section");
+        parent.appendChild(detailsSection);
+
+        title = document.createElement("title");
+        detailsSection.appendChild(title);
+        title.appendChild(document.createTextNode("Script block details"));
+
+        for (BlockDoc blockDoc : classBlocks) {
+            blockDetailRenderer.renderTo(blockDoc, detailsSection);
+        }
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            for (BlockDoc blockDoc : extensionDoc.getExtensionBlocks()) {
+                blockDetailRenderer.renderTo(blockDoc, detailsSection);
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDescriptionRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDescriptionRenderer.java
new file mode 100644
index 0000000..6870357
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDescriptionRenderer.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dsl.docbook.model.ClassDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class ClassDescriptionRenderer {
+    private final ElementWarningsRenderer warningsRenderer = new ElementWarningsRenderer();
+
+    public void renderTo(ClassDoc classDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element title = document.createElement("title");
+        parent.appendChild(title);
+        title.appendChild(document.createTextNode(classDoc.getSimpleName()));
+
+        Element list = document.createElement("segmentedlist");
+        parent.appendChild(list);
+        Element segtitle = document.createElement("segtitle");
+        list.appendChild(segtitle);
+        segtitle.appendChild(document.createTextNode("API Documentation"));
+        Element listItem = document.createElement("seglistitem");
+        list.appendChild(listItem);
+        Element seg = document.createElement("seg");
+        listItem.appendChild(seg);
+        Element apilink = document.createElement("apilink");
+        seg.appendChild(apilink);
+        apilink.setAttribute("class", classDoc.getName());
+        apilink.setAttribute("style", classDoc.getStyle());
+
+        warningsRenderer.renderTo(classDoc, "class", parent);
+
+        for (Element element : classDoc.getComment()) {
+            parent.appendChild(document.importNode(element, true));
+        }
+        NodeList otherContent = classDoc.getClassSection().getChildNodes();
+        for (int i = 0; i < otherContent.getLength(); i++) {
+            Node child = otherContent.item(i);
+            if (child instanceof Element && !((Element) child).getTagName().equals("section")) {
+                parent.appendChild(document.importNode(child, true));
+            }
+        }
+    }
+}
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
deleted file mode 100644
index 5dfd3e9..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
+++ /dev/null
@@ -1,343 +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 org.gradle.build.docs.dsl.model.ClassMetaData
-import org.gradle.build.docs.dsl.model.PropertyMetaData
-import org.w3c.dom.Document
-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 implements DslElementDoc {
-    private final String className
-    private final String id
-    private final String simpleName
-    final ClassMetaData classMetaData
-    private final Element classSection
-    private final ClassExtensionMetaData extensionMetaData
-    private final List<PropertyDoc> classProperties = []
-    private final List<MethodDoc> classMethods = []
-    private final List<BlockDoc> classBlocks = []
-    private final List<ClassExtensionDoc> classExtensions = []
-    private final JavadocConverter javadocConverter
-    private final DslDocModel model
-    private final Element propertiesTable
-    private final Element methodsTable
-    private final Element propertiesSection
-    private final Element methodsSection
-    private List<Element> comment
-    private final GenerationListener listener = new DefaultGenerationListener()
-
-    ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ClassExtensionMetaData extensionMetaData, DslDocModel model, JavadocConverter javadocConverter) {
-        this.className = className
-        id = className
-        simpleName = className.tokenize('.').last()
-        this.classMetaData = classMetaData
-        this.javadocConverter = javadocConverter
-        this.model = model
-        this.extensionMetaData = extensionMetaData
-
-        classSection = targetDocument.createElement('chapter')
-
-        classContent.childNodes.each { Node n ->
-            classSection << n
-        }
-
-        propertiesTable = getTable('Properties')
-        propertiesSection = propertiesTable.parentNode
-        methodsTable = getTable('Methods')
-        methodsSection = methodsTable.parentNode
-    }
-
-    String getId() { return id }
-
-    def getName() { return className }
-
-    def getSimpleName() { return simpleName }
-
-    List<Element> getComment() { return comment }
-
-    boolean isDeprecated() {
-        return classMetaData.deprecated
-    }
-
-    boolean isExperimental() {
-        return classMetaData.experimental
-    }
-
-    def getClassProperties() { return classProperties }
-
-    def getClassMethods() { return classMethods }
-
-    def getClassBlocks() { return classBlocks }
-
-    def getClassExtensions() { return classExtensions }
-
-    def getClassSection() { return classSection }
-
-    def getPropertiesTable() { return propertiesTable }
-
-    def getPropertiesSection() { return propertiesSection }
-
-    def getPropertyDetailsSection() { return getSection('Property details') }
-
-    def getMethodsTable() { return methodsTable }
-
-    def getMethodsSection() { return methodsSection }
-
-    def getMethodDetailsSection() { return getSection('Method details') }
-
-    def getBlocksTable() { return getTable('Script blocks') }
-
-    def getBlockDetailsSection() { return getSection('Script block details') }
-
-    ClassDoc mergeContent() {
-        buildDescription()
-        buildProperties()
-        buildMethods()
-        buildExtensions()
-        return this
-    }
-
-    ClassDoc buildDescription() {
-        comment = javadocConverter.parse(classMetaData, listener).docbook
-        return this
-    }
-
-    ClassDoc buildProperties() {
-        List<Element> header = propertiesTable.thead.tr[0].td.collect { it }
-        if (header.size() < 1) {
-            throw new RuntimeException("Expected at least 1 <td> in <thead>/<tr>, found: $header")
-        }
-        Map<String, Element> inheritedValueTitleMapping = [:]
-        List<Element> valueTitles = []
-        header.eachWithIndex { element, index ->
-            if (index == 0) { return }
-            Element override = element.overrides[0]
-            if (override) {
-                element.removeChild(override)
-                inheritedValueTitleMapping.put(override.textContent, element)
-            }
-            if (element.firstChild instanceof Text) {
-                element.firstChild.textContent = element.firstChild.textContent.replaceFirst(/^\s+/, '')
-            }
-            if (element.lastChild instanceof Text) {
-                element.lastChild.textContent = element.lastChild.textContent.replaceFirst(/\s+$/, '')
-            }
-            valueTitles.add(element)
-        }
-
-        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 ->
-                def additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>()
-                propertyDoc.additionalValues.each { attributeDoc ->
-                    def key = attributeDoc.key
-                    if (inheritedValueTitleMapping[key]) {
-                        ExtraAttributeDoc newAttribute = new ExtraAttributeDoc(inheritedValueTitleMapping[key], attributeDoc.valueCell)
-                        additionalValues.put(newAttribute.key, newAttribute)
-                    } else {
-                        additionalValues.put(key, attributeDoc)
-                    }
-                }
-
-                props[propertyDoc.name] = propertyDoc.forClass(classMetaData, additionalValues.values() as List)
-            }
-        }
-
-        propertiesTable.tr.each { Element tr ->
-            def cells = tr.td.collect { it }
-            if (cells.size() != header.size()) {
-                throw new RuntimeException("Expected ${header.size()} <td> elements in <tr>, found: $tr")
-            }
-            String propName = cells[0].text().trim()
-            PropertyMetaData property = classMetaData.findProperty(propName)
-            if (!property) {
-                throw new RuntimeException("No metadata for property '$className.$propName'. Available properties: ${classMetaData.propertyNames}")
-            }
-
-            def additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>()
-
-            if (superClass) {
-                def overriddenProp = props.get(propName)
-                if (overriddenProp) {
-                    overriddenProp.additionalValues.each { attributeDoc ->
-                        additionalValues.put(attributeDoc.key, attributeDoc)
-                    }
-                }
-            }
-
-            header.eachWithIndex { col, i ->
-                if (i == 0 || !cells[i].firstChild) { return }
-                def attributeDoc = new ExtraAttributeDoc(valueTitles[i-1], cells[i])
-                additionalValues.put(attributeDoc.key, attributeDoc)
-            }
-            PropertyDoc propertyDoc = new PropertyDoc(property, javadocConverter.parse(property, listener).docbook, additionalValues.values() as List)
-            if (propertyDoc.description == null) {
-                throw new RuntimeException("Docbook content for '$className.$propName' does not contain a description paragraph.")
-            }
-
-            props[propName] = propertyDoc
-        }
-
-        classProperties.addAll(props.values())
-
-        return this
-    }
-
-    ClassDoc buildMethods() {
-        Set signatures = [] as Set
-
-        methodsTable.tr.each { Element tr ->
-            def cells = tr.td
-            if (cells.size() != 1) {
-                throw new RuntimeException("Expected 1 cell in <tr>, found: $tr")
-            }
-            String methodName = cells[0].text().trim()
-            Collection<MethodMetaData> methods = classMetaData.declaredMethods.findAll { it.name == methodName }
-            if (!methods) {
-                throw new RuntimeException("No metadata for method '$className.$methodName()'. Available methods: ${classMetaData.declaredMethods.collect {it.name} as TreeSet}")
-            }
-            methods.each { method ->
-                def methodDoc = new MethodDoc(method, javadocConverter.parse(method, listener).docbook)
-                if (!methodDoc.description) {
-                    throw new RuntimeException("Docbook content for '$className $method.signature' does not contain a description paragraph.")
-                }
-                def property = findProperty(method.name)
-                def multiValued = false
-                if (method.parameters.size() == 1 && method.parameters[0].type.signature == Closure.class.name && property) {
-                    def type = property.metaData.type
-                    if (type.name == 'java.util.List' || type.name == 'java.util.Collection' || type.name == 'java.util.Set' || type.name == 'java.util.Iterable') {
-                        type = type.typeArgs[0]
-                        multiValued = true
-                    }
-                    classBlocks << new BlockDoc(methodDoc, property, type, multiValued)
-                } else {
-                    classMethods << methodDoc
-                    signatures << method.overrideSignature
-                }
-            }
-        }
-
-        if (classMetaData.superClassName) {
-            ClassDoc supertype = model.getClassDoc(classMetaData.superClassName)
-            supertype.getClassMethods().each { method ->
-                if (signatures.add(method.metaData.overrideSignature)) {
-                    classMethods << method.forClass(classMetaData)
-                }
-            }
-        }
-
-        classMethods.sort { it.metaData.overrideSignature }
-        classBlocks.sort { it.name }
-
-        return this
-    }
-
-    ClassDoc buildExtensions() {
-        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
-            }
-            classExtensionDoc.extensionClasses[extension.extensionId] = model.getClassDoc(extension.extensionClass)
-        }
-
-        classExtensions.addAll(plugins.values())
-        classExtensions.each { extension -> extension.buildMetaData(model) }
-        classExtensions.sort { it.pluginId }
-
-        return this
-    }
-
-    String getStyle() {
-        return classMetaData.groovy ? 'groovydoc' : 'javadoc'
-    }
-
-    private Element getTable(String title) {
-        def table = getSection(title).table[0]
-        if (!table) {
-            throw new RuntimeException("Section '$title' does not contain a <table> element.")
-        }
-        if (!table.thead[0]) {
-            throw new RuntimeException("Table '$title' does not contain a <thead> element.")
-        }
-        if (!table.thead[0].tr[0]) {
-            throw new RuntimeException("Table '$title' does not contain a <thead>/<tr> element.")
-        }
-        return table
-    }
-
-    private Element getSection(String title) {
-        def sections = classSection.section.findAll {
-            it.title[0] && it.title[0].text().trim() == title
-        }
-        if (sections.size() < 1) {
-            throw new RuntimeException("Docbook content for $className does not contain a '$title' section.")
-        }
-        return sections[0]
-    }
-
-    Element getHasDescription() {
-        def paras = classSection.para
-        return paras.size() > 0 ? paras[0] : null
-    }
-
-    Element getDescription() {
-        def paras = classSection.para
-        if (paras.size() < 1) {
-            throw new RuntimeException("Docbook content for $className does not contain a description paragraph.")
-        }
-        return paras[0]
-    }
-
-    PropertyDoc findProperty(String name) {
-        return classProperties.find { it.name == name }
-    }
-
-    BlockDoc getBlock(String name) {
-        def block = classBlocks.find { it.name == name }
-        if (block) {
-            return block
-        }
-        for (extensionDoc in classExtensions) {
-            block = extensionDoc.extensionBlocks.find { it.name == name }
-            if (block) {
-                return block
-            }
-        }
-        throw new RuntimeException("Class $className does not have a script block '$name'.")
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocBuilder.java
new file mode 100644
index 0000000..a0b3854
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocBuilder.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+
+public class ClassDocBuilder {
+    private final ClassDocCommentBuilder commentBuilder;
+    private final ClassDocPropertiesBuilder propertiesBuilder;
+    private final ClassDocMethodsBuilder methodsBuilder;
+    private final ClassDocExtensionsBuilder extensionsBuilder;
+    private final ClassDocSuperTypeBuilder superTypeBuilder;
+    private final GenerationListener listener = new DefaultGenerationListener();
+
+    public ClassDocBuilder(DslDocModel model, JavadocConverter javadocConverter) {
+        commentBuilder = new ClassDocCommentBuilder(javadocConverter, listener);
+        propertiesBuilder = new ClassDocPropertiesBuilder(javadocConverter, listener);
+        methodsBuilder = new ClassDocMethodsBuilder(javadocConverter, listener);
+        extensionsBuilder = new ClassDocExtensionsBuilder(model, listener);
+        superTypeBuilder = new ClassDocSuperTypeBuilder(model, listener);
+    }
+
+    void build(ClassDoc classDoc) {
+        listener.start(String.format("class %s", classDoc.getName()));
+        try {
+            superTypeBuilder.build(classDoc);
+            commentBuilder.build(classDoc);
+            propertiesBuilder.build(classDoc);
+            methodsBuilder.build(classDoc);
+            extensionsBuilder.build(classDoc);
+            classDoc.mergeContent();
+        } finally {
+            listener.finish();
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocCommentBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocCommentBuilder.java
new file mode 100644
index 0000000..c2d02c3
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocCommentBuilder.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+
+public class ClassDocCommentBuilder {
+    private final JavadocConverter javadocConverter;
+    private final GenerationListener listener;
+
+    public ClassDocCommentBuilder(JavadocConverter javadocConverter, GenerationListener listener) {
+        this.javadocConverter = javadocConverter;
+        this.listener = listener;
+    }
+
+    /**
+     * Builds the class comment for the given class.
+     */
+    void build(ClassDoc classDoc) {
+        classDoc.setComment(javadocConverter.parse(classDoc.getClassMetaData(), listener).getDocbook());
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilder.java
new file mode 100644
index 0000000..c2a9ea0
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilder.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.build.docs.dsl.docbook;
+
+import groovy.lang.Closure;
+import org.gradle.build.docs.dsl.docbook.model.*;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData;
+import org.gradle.build.docs.dsl.source.model.TypeMetaData;
+import org.gradle.internal.UncheckedException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ClassDocExtensionsBuilder {
+    private final DslDocModel model;
+    private final GenerationListener listener;
+
+    public ClassDocExtensionsBuilder(DslDocModel model, GenerationListener listener) {
+        this.model = model;
+        this.listener = listener;
+    }
+
+    /**
+     * Builds the extension meta-data for the given class.
+     */
+    public void build(ClassDoc classDoc) {
+        Map<String, ClassExtensionDoc> plugins = new HashMap<String, ClassExtensionDoc>();
+        for (MixinMetaData mixin : classDoc.getExtensionMetaData().getMixinClasses()) {
+            String pluginId = mixin.getPluginId();
+            ClassExtensionDoc classExtensionDoc = plugins.get(pluginId);
+            if (classExtensionDoc == null) {
+                classExtensionDoc = new ClassExtensionDoc(pluginId, classDoc);
+                plugins.put(pluginId, classExtensionDoc);
+            }
+            classExtensionDoc.getMixinClasses().add(model.getClassDoc(mixin.getMixinClass()));
+        }
+        for (ExtensionMetaData extension : classDoc.getExtensionMetaData().getExtensionClasses()) {
+            String pluginId = extension.getPluginId();
+            ClassExtensionDoc classExtensionDoc = plugins.get(pluginId);
+            if (classExtensionDoc == null) {
+                classExtensionDoc = new ClassExtensionDoc(pluginId, classDoc);
+                plugins.put(pluginId, classExtensionDoc);
+            }
+            classExtensionDoc.getExtensionClasses().put(extension.getExtensionId(), model.getClassDoc(extension.getExtensionClass()));
+        }
+        for (ClassExtensionDoc extensionDoc : plugins.values()) {
+            build(extensionDoc);
+            classDoc.addClassExtension(extensionDoc);
+        }
+    }
+
+    private void build(ClassExtensionDoc extensionDoc) {
+        Document doc;
+        try {
+            doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+        } catch (ParserConfigurationException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        LinkRenderer linkRenderer = new LinkRenderer(doc, model);
+        for (Map.Entry<String, ClassDoc> entry : extensionDoc.getExtensionClasses().entrySet()) {
+            String id = entry.getKey();
+            ClassDoc type = entry.getValue();
+            PropertyMetaData propertyMetaData = new PropertyMetaData(id, extensionDoc.getTargetClass().getClassMetaData());
+            propertyMetaData.setType(new TypeMetaData(type.getName()));
+
+            Element para = doc.createElement("para");
+            para.appendChild(doc.createTextNode("The "));
+            para.appendChild(linkRenderer.link(propertyMetaData.getType(), listener));
+            para.appendChild(doc.createTextNode(String.format(" added by the %s plugin.", extensionDoc.getPluginId())));
+
+            PropertyDoc propertyDoc = new PropertyDoc(propertyMetaData, Collections.singletonList(para), Collections.<ExtraAttributeDoc>emptyList());
+            extensionDoc.getExtraProperties().add(propertyDoc);
+
+            para = doc.createElement("para");
+            para.appendChild(doc.createTextNode("Configures the "));
+            para.appendChild(linkRenderer.link(propertyMetaData.getType(), listener));
+            para.appendChild(doc.createTextNode(String.format(" added by the %s plugin.", extensionDoc.getPluginId())));
+
+            MethodMetaData methodMetaData = new MethodMetaData(id, extensionDoc.getTargetClass().getClassMetaData());
+            methodMetaData.addParameter("configClosure", new TypeMetaData(Closure.class.getName()));
+            MethodDoc methodDoc = new MethodDoc(methodMetaData, Collections.singletonList(para));
+            extensionDoc.getExtraBlocks().add(new BlockDoc(methodDoc, propertyDoc, propertyMetaData.getType(), false));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilder.java
new file mode 100644
index 0000000..01ee7fc
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 groovy.lang.Closure;
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc;
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc;
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.dsl.source.model.TypeMetaData;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ClassDocMethodsBuilder extends ModelBuilderSupport {
+    private final JavadocConverter javadocConverter;
+    private final GenerationListener listener;
+
+    public ClassDocMethodsBuilder(JavadocConverter converter, GenerationListener listener) {
+        this.javadocConverter = converter;
+        this.listener = listener;
+    }
+
+    /**
+     * Builds the methods and script blocks of the given class. Assumes properties have already been built.
+     */
+    public void build(ClassDoc classDoc) {
+        Set<String> signatures = new HashSet<String>();
+
+        for (Element tr : children(classDoc.getMethodsTable(), "tr")) {
+            List<Element> cells = children(tr, "td");
+            if (cells.size() != 1) {
+                throw new RuntimeException(String.format("Expected 1 cell in <tr>, found: %s", tr));
+            }
+            String methodName = cells.get(0).getTextContent().trim();
+            Collection<MethodMetaData> methods = classDoc.getClassMetaData().findDeclaredMethods(methodName);
+            if (methods.isEmpty()) {
+                throw new RuntimeException(String.format("No metadata for method '%s.%s()'. Available methods: %s", classDoc.getName(), methodName, classDoc.getClassMetaData().getDeclaredMethodNames()));
+            }
+            for (MethodMetaData method : methods) {
+                DocComment docComment = javadocConverter.parse(method, listener);
+                MethodDoc methodDoc = new MethodDoc(method, docComment.getDocbook());
+                if (methodDoc.getDescription() == null) {
+                    throw new RuntimeException(String.format("Docbook content for '%s %s' does not contain a description paragraph.", classDoc.getName(), method.getSignature()));
+                }
+                PropertyDoc property = classDoc.findProperty(methodName);
+                boolean multiValued = false;
+                if (property != null && method.getParameters().size() == 1 && method.getParameters().get(0).getType().getSignature().equals(Closure.class.getName())) {
+                    TypeMetaData type = property.getMetaData().getType();
+                    if (type.getName().equals("java.util.List")
+                            || type.getName().equals("java.util.Collection")
+                            || type.getName().equals("java.util.Set")
+                            || type.getName().equals("java.util.Iterable")) {
+                        type = type.getTypeArgs().get(0);
+                        multiValued = true;
+                    }
+                    classDoc.addClassBlock(new BlockDoc(methodDoc, property, type, multiValued));
+                } else {
+                    classDoc.addClassMethod(methodDoc);
+                    signatures.add(method.getOverrideSignature());
+                }
+            }
+        }
+
+        ClassDoc supertype = classDoc.getSuperClass();
+        if (supertype != null) {
+            for (MethodDoc method: supertype.getClassMethods()){
+                if (signatures.add(method.getMetaData().getOverrideSignature())) {
+                    classDoc.addClassMethod(method);
+                }
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilder.java
new file mode 100644
index 0000000..3e21967
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilder.java
@@ -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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.ExtraAttributeDoc;
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+import java.util.*;
+
+public class ClassDocPropertiesBuilder extends ModelBuilderSupport {
+    private final JavadocConverter javadocConverter;
+    private final GenerationListener listener;
+
+    public ClassDocPropertiesBuilder(JavadocConverter javadocConverter, GenerationListener listener) {
+        this.javadocConverter = javadocConverter;
+        this.listener = listener;
+    }
+
+    /**
+     * Builds the properties of the given class
+     */
+    void build(ClassDoc classDoc) {
+        Element thead = getChild(classDoc.getPropertiesTable(), "thead");
+        Element tr = getChild(thead, "tr");
+        List<Element> header = children(tr, "td");
+        if (header.size() < 1) {
+            throw new RuntimeException(String.format("Expected at least 1 <td> in <thead>/<tr>, found: %s", header));
+        }
+        Map<String, Element> inheritedValueTitleMapping = new HashMap<String, Element>();
+        List<Element> valueTitles = new ArrayList<Element>();
+        for (int i = 1; i < header.size(); i++) {
+            Element element = header.get(i);
+            Element override = findChild(element, "overrides");
+            if (override != null) {
+                element.removeChild(override);
+                inheritedValueTitleMapping.put(override.getTextContent(), element);
+            }
+            Node firstChild = element.getFirstChild();
+            if (firstChild instanceof Text) {
+                firstChild.setTextContent(firstChild.getTextContent().replaceFirst("^\\s+", ""));
+            }
+            Node lastChild = element.getLastChild();
+            if (lastChild instanceof Text) {
+                lastChild.setTextContent(lastChild.getTextContent().replaceFirst("\\s+$", ""));
+            }
+            valueTitles.add(element);
+        }
+
+        ClassDoc superClass = classDoc.getSuperClass();
+
+        //adding the properties from the super class onto the inheriting class
+        Map<String, PropertyDoc> props = new TreeMap<String, PropertyDoc>();
+        if (superClass != null) {
+            for (PropertyDoc propertyDoc : superClass.getClassProperties()) {
+                Map<String, ExtraAttributeDoc> additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>();
+                for (ExtraAttributeDoc attributeDoc : propertyDoc.getAdditionalValues()) {
+                    String key = attributeDoc.getKey();
+                    if (inheritedValueTitleMapping.get(key) != null) {
+                        ExtraAttributeDoc newAttribute = new ExtraAttributeDoc(inheritedValueTitleMapping.get(key), attributeDoc.getValueCell());
+                        additionalValues.put(newAttribute.getKey(), newAttribute);
+                    } else {
+                        additionalValues.put(key, attributeDoc);
+                    }
+                }
+
+                props.put(propertyDoc.getName(), propertyDoc.forClass(classDoc, additionalValues.values()));
+            }
+        }
+
+        for (Element row : children(classDoc.getPropertiesTable(), "tr")) {
+            List<Element> cells = children(row, "td");
+            if (cells.size() != header.size()) {
+                throw new RuntimeException(String.format("Expected %s <td> elements in <tr>, found: %s", header.size(), tr));
+            }
+            String propName = cells.get(0).getTextContent().trim();
+            PropertyMetaData property = classDoc.getClassMetaData().findProperty(propName);
+            if (property == null) {
+                throw new RuntimeException(String.format("No metadata for property '%s.%s'. Available properties: %s", classDoc.getName(), propName, classDoc.getClassMetaData().getPropertyNames()));
+            }
+
+            Map<String, ExtraAttributeDoc> additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>();
+
+            if (superClass != null) {
+                PropertyDoc overriddenProp = props.get(propName);
+                if (overriddenProp != null) {
+                    for (ExtraAttributeDoc attributeDoc : overriddenProp.getAdditionalValues()) {
+                        additionalValues.put(attributeDoc.getKey(), attributeDoc);
+                    }
+                }
+            }
+
+            for (int i = 1; i < header.size(); i++) {
+                if (cells.get(i).getFirstChild() == null) {
+                    continue;
+                }
+                ExtraAttributeDoc attributeDoc = new ExtraAttributeDoc(valueTitles.get(i - 1), cells.get(i));
+                additionalValues.put(attributeDoc.getKey(), attributeDoc);
+            }
+            PropertyDoc propertyDoc = new PropertyDoc(property, javadocConverter.parse(property, listener).getDocbook(), new ArrayList<ExtraAttributeDoc>(additionalValues.values()));
+            if (propertyDoc.getDescription() == null) {
+                throw new RuntimeException(String.format("Docbook content for '%s.%s' does not contain a description paragraph.", classDoc.getName(), propName));
+            }
+
+            props.put(propName, propertyDoc);
+        }
+
+        for (PropertyDoc propertyDoc : props.values()) {
+            classDoc.addClassProperty(propertyDoc);
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy
deleted file mode 100644
index 8797f14..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy
+++ /dev/null
@@ -1,443 +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
-
-class ClassDocRenderer {
-    private final LinkRenderer linkRenderer
-    private final GenerationListener listener = new DefaultGenerationListener()
-
-    ClassDocRenderer(LinkRenderer linkRenderer) {
-        this.linkRenderer = linkRenderer
-    }
-
-    void mergeContent(ClassDoc classDoc) {
-        listener.start("class $classDoc.className")
-        try {
-            mergeDescription(classDoc)
-            mergeProperties(classDoc)
-            mergeMethods(classDoc)
-            mergeBlocks(classDoc)
-            mergeExtensions(classDoc)
-        } finally {
-            listener.finish()
-        }
-    }
-
-    void mergeDescription(ClassDoc classDoc) {
-        def classContent = classDoc.classSection
-        classContent.setAttribute('id', classDoc.id)
-        classContent.addFirst {
-            title(classDoc.simpleName)
-            segmentedlist {
-                segtitle('API Documentation')
-                seglistitem {
-                    seg { apilink('class': classDoc.name, style: classDoc.style) }
-                }
-            }
-            addWarnings(classDoc, 'class', delegate)
-            appendChildren classDoc.comment
-        }
-    }
-
-    void mergeProperties(ClassDoc classDoc) {
-        def propertiesTable = classDoc.propertiesTable
-        def propertiesSection = classDoc.propertiesSection
-        def classProperties = classDoc.classProperties
-
-        if (classProperties.isEmpty()) {
-            propertiesSection.children = {
-                title('Properties')
-                para('No properties')
-            }
-            return
-        }
-
-        propertiesTable.children = {
-            title("Properties - $classDoc.simpleName")
-            thead {
-                tr { td('Property'); td('Description') }
-            }
-            classProperties.each { propDoc ->
-                tr {
-                    td { link(linkend: propDoc.id) { literal(propDoc.name) } }
-                    td { appendChild(propDoc.description) }
-                }
-            }
-        }
-
-        propertiesSection.addAfter {
-            section {
-                title('Property details')
-                classProperties.each { propDoc ->
-                    section(id: propDoc.id, role: 'detail') {
-                        title {
-                            appendChild linkRenderer.link(propDoc.metaData.type, listener)
-                            text(' ')
-                            literal(propDoc.name)
-                            if (!propDoc.metaData.writeable) {
-                                text(' (read-only)')
-                            }
-                        }
-                        addWarnings(propDoc, 'property', delegate)
-                        appendChildren propDoc.comment
-                        if (propDoc.additionalValues) {
-                            segmentedlist {
-                                propDoc.additionalValues.each { attributeDoc ->
-                                    segtitle { appendChildren(attributeDoc.title) }
-                                }
-                                seglistitem {
-                                    propDoc.additionalValues.each { ExtraAttributeDoc attributeDoc ->
-                                        seg { appendChildren(attributeDoc.value) }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    void mergeMethods(ClassDoc classDoc) {
-        def methodsSection = classDoc.methodsSection
-        def methodsTable = classDoc.methodsTable
-        def classMethods = classDoc.classMethods
-
-        if (classMethods.isEmpty()) {
-            methodsSection.children = {
-                title('Methods')
-                para('No methods')
-            }
-            return
-        }
-
-        methodsTable.children = {
-            title("Methods - $classDoc.simpleName")
-            thead {
-                tr {
-                    td('Method')
-                    td('Description')
-                }
-            }
-            classMethods.each { method ->
-                tr {
-                    td {
-                        literal {
-                            link(linkend: method.id) { text(method.name) }
-                            text('(')
-                            method.metaData.parameters.eachWithIndex { param, index ->
-                                if ( index > 0 ) {
-                                    text(', ')
-                                }
-                                text(param.name)
-                            }
-                            text(')')
-                        }
-                    }
-                    td { appendChild method.description }
-                }
-            }
-        }
-
-        methodsSection.addAfter {
-            section {
-                title('Method details')
-                classMethods.each { method ->
-                    section(id: method.id, role: 'detail') {
-                        title {
-                            appendChild linkRenderer.link(method.metaData.returnType, listener)
-                            text(' ')
-                            literal(method.name)
-                            text('(')
-                            method.metaData.parameters.eachWithIndex {param, i ->
-                                if (i > 0) {
-                                    text(', ')
-                                }
-                                appendChild linkRenderer.link(param.type, listener)
-                                text(" $param.name")
-                            }
-                            text(')')
-                        }
-                        addWarnings(method, 'method', delegate)
-                        appendChildren method.comment
-                    }
-                }
-            }
-        }
-    }
-
-    void mergeBlocks(ClassDoc classDoc) {
-        def targetSection = classDoc.methodsSection
-        def classBlocks = classDoc.classBlocks
-
-        if (classBlocks.isEmpty()) {
-            targetSection.addBefore {
-                section {
-                    title('Script blocks')
-                    para('No script blocks')
-                }
-            }
-            return
-        }
-
-        targetSection.addBefore {
-            section {
-                title('Script blocks')
-                table {
-                    title("Script blocks - $classDoc.simpleName")
-                    thead {
-                        tr {
-                            td('Block'); td('Description')
-                        }
-                    }
-                    classBlocks.each { block ->
-                        tr {
-                            td { link(linkend: block.id) { literal(block.name) } }
-                            td { appendChild block.description }
-                        }
-                    }
-                }
-            }
-            section {
-                title('Script block details')
-                classBlocks.each { block ->
-                    section(id: block.id, role: 'detail') {
-                        title {
-                            literal(block.name); text(' { }')
-                        }
-                        addWarnings(block, 'script block', delegate)
-                        appendChildren block.comment
-                        segmentedlist {
-                            segtitle('Delegates to')
-                            seglistitem {
-                                seg {
-                                    if (block.multiValued) {
-                                        text('Each ')
-                                        appendChild linkRenderer.link(block.type, listener)
-                                        text(' in ')
-                                        link(linkend: block.blockProperty.id) { literal(block.blockProperty.name) }
-                                    } else {
-                                        appendChild linkRenderer.link(block.type, listener)
-                                        text(' from ')
-                                        link(linkend: block.blockProperty.id) { literal(block.blockProperty.name) }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    void addWarnings(DslElementDoc elementDoc, String description, Object delegate) {
-        if (elementDoc.deprecated) {
-            delegate.caution {
-                para{
-                    text("Note: This $description is ")
-                    link(linkend: 'dsl-element-types', "deprecated")
-                    text(" and will be removed in the next major version of Gradle.")
-                }
-            }
-        }
-        if (elementDoc.experimental) {
-            delegate.caution {
-                para{
-                    text("Note: This $description is ")
-                    link(linkend: 'dsl-element-types', "experimental")
-                    text(" and may change in a future version of Gradle.")
-                }
-            }
-        }
-    }
-
-    void mergeExtensions(ClassDoc classDoc) {
-        if (!classDoc.classExtensions) {
-            return
-        }
-        mergeExtensionProperties(classDoc)
-        mergeExtensionMethods(classDoc)
-        mergeExtensionBlocks(classDoc)
-    }
-
-    void mergeExtensionProperties(ClassDoc classDoc) {
-        classDoc.propertiesTable.addAfter {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                if (!extension.extensionProperties) {
-                    return
-                }
-                section {
-                    title { text("Properties added by the "); literal(extension.pluginId); text(" plugin") }
-                    titleabbrev { literal(extension.pluginId); text(" plugin") }
-                    table {
-                        title { text("Properties - "); literal(extension.pluginId); text(" plugin") }
-                        thead { tr { td('Property'); td('Description') } }
-                        extension.extensionProperties.each { propertyDoc ->
-                            tr {
-                                td { link(linkend: propertyDoc.id) { literal(propertyDoc.name) } }
-                                td { appendChild propertyDoc.description }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        classDoc.propertyDetailsSection << {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                extension.extensionProperties.each { propDoc ->
-                    section(id: propDoc.id, role: 'detail') {
-                        title {
-                            appendChild linkRenderer.link(propDoc.metaData.type, listener)
-                            text(' ')
-                            literal(propDoc.name)
-                            if (!propDoc.metaData.writeable) {
-                                text(' (read-only)')
-                            }
-                        }
-                        appendChildren propDoc.comment
-                        if (propDoc.additionalValues) {
-                            segmentedlist {
-                                propDoc.additionalValues.each { attributeDoc ->
-                                    segtitle { appendChildren(attributeDoc.title) }
-                                }
-                                seglistitem {
-                                    propDoc.additionalValues.each { ExtraAttributeDoc attributeDoc ->
-                                        seg { appendChildren(attributeDoc.value) }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    void mergeExtensionMethods(ClassDoc classDoc) {
-        classDoc.methodsTable.addAfter {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                if (!extension.extensionMethods) {
-                    return
-                }
-                section {
-                    title { text("Methods added by the "); literal(extension.pluginId); text(" plugin") }
-                    titleabbrev { literal(extension.pluginId); text(" plugin") }
-                    table {
-                        title { text("Methods - "); literal(extension.pluginId); text(" plugin") }
-                        thead { tr { td('Method'); td('Description') } }
-                        extension.extensionMethods.each { method ->
-                            tr {
-                                td {
-                                    literal {
-                                        link(linkend: method.id) { text(method.name) }
-                                        text('(')
-                                        method.metaData.parameters.eachWithIndex { param, index ->
-                                            if ( index > 0 ) {
-                                                text(', ')
-                                            }
-                                            text(param.name)
-                                        }
-                                        text(')')
-                                    }
-                                }
-                                td { appendChild method.description }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        classDoc.methodDetailsSection << {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                extension.extensionMethods.each { method ->
-                    section(id: method.id, role: 'detail') {
-                        title {
-                            appendChild linkRenderer.link(method.metaData.returnType, listener)
-                            text(' ')
-                            literal(method.name)
-                            text('(')
-                            method.metaData.parameters.eachWithIndex {param, i ->
-                                if (i > 0) {
-                                    text(', ')
-                                }
-                                appendChild linkRenderer.link(param.type, listener)
-                                text(" $param.name")
-                            }
-                            text(')')
-                        }
-                        appendChildren method.comment
-                    }
-                }
-            }
-        }
-    }
-
-    void mergeExtensionBlocks(ClassDoc classDoc) {
-        classDoc.blocksTable.addAfter {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                if (!extension.extensionBlocks) {
-                    return
-                }
-                section {
-                    title { text("Script blocks added by the "); literal(extension.pluginId); text(" plugin") }
-
-                    titleabbrev { literal(extension.pluginId); text(" plugin") }
-                    table {
-                        title { text("Script blocks - "); literal(extension.pluginId); text(" plugin") }
-                        thead { tr { td('Block'); td('Description') } }
-                        extension.extensionBlocks.each { blockDoc ->
-                            tr {
-                                td { link(linkend: blockDoc.id) { literal(blockDoc.name) } }
-                                td { appendChild blockDoc.description }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        classDoc.blockDetailsSection << {
-            classDoc.classExtensions.each { ClassExtensionDoc extension ->
-                extension.extensionBlocks.each { block ->
-                    section(id: block.id, role: 'detail') {
-                        title {
-                            literal(block.name); text(' { }')
-                        }
-                        appendChildren block.comment
-                        segmentedlist {
-                            segtitle('Delegates to')
-                            seglistitem {
-                                seg {
-                                    if (block.multiValued) {
-                                        text('Each ')
-                                        appendChild linkRenderer.link(block.type, listener)
-                                        text(' in ')
-                                        link(linkend: block.blockProperty.id) { literal(block.blockProperty.name) }
-                                    } else {
-                                        appendChild linkRenderer.link(block.type, listener)
-                                        text(' from ')
-                                        link(linkend: block.blockProperty.id) { literal(block.blockProperty.name) }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.java
new file mode 100644
index 0000000..f26b1d1
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.java
@@ -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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.w3c.dom.Element;
+
+public class ClassDocRenderer {
+    private final GenerationListener listener = new DefaultGenerationListener();
+    private final ClassDescriptionRenderer descriptionRenderer = new ClassDescriptionRenderer();
+    private final PropertiesRenderer propertiesRenderer;
+    private final MethodsRenderer methodsRenderer;
+    final BlocksRenderer blocksRenderer;
+
+    public ClassDocRenderer(LinkRenderer linkRenderer) {
+        propertiesRenderer = new PropertiesRenderer(linkRenderer, listener);
+        methodsRenderer = new MethodsRenderer(linkRenderer, listener);
+        blocksRenderer = new BlocksRenderer(linkRenderer, listener);
+    }
+
+    public void mergeContent(ClassDoc classDoc, Element parent) {
+        listener.start(String.format("class %s", classDoc.getName()));
+        try {
+            Element chapter = parent.getOwnerDocument().createElement("chapter");
+            parent.appendChild(chapter);
+            chapter.setAttribute("id", classDoc.getId());
+            descriptionRenderer.renderTo(classDoc, chapter);
+            mergeProperties(classDoc, chapter);
+            mergeBlocks(classDoc, chapter);
+            mergeMethods(classDoc, chapter);
+        } finally {
+            listener.finish();
+        }
+    }
+
+    void mergeProperties(ClassDoc classDoc, Element classContent) {
+        propertiesRenderer.renderTo(classDoc, classContent);
+    }
+
+    void mergeMethods(ClassDoc classDoc, Element classContent) {
+        methodsRenderer.renderTo(classDoc, classContent);
+    }
+
+    void mergeBlocks(ClassDoc classDoc, Element classContent) {
+        blocksRenderer.renderTo(classDoc, classContent);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocSuperTypeBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocSuperTypeBuilder.java
new file mode 100644
index 0000000..bb00b36
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocSuperTypeBuilder.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClassDocSuperTypeBuilder {
+    private final DslDocModel model;
+    private final GenerationListener listener;
+
+    public ClassDocSuperTypeBuilder(DslDocModel model, GenerationListener listener) {
+        this.model = model;
+        this.listener = listener;
+    }
+
+    /**
+     * Builds and attaches the supertype of the given class
+     */
+    void build(ClassDoc classDoc) {
+        ClassMetaData classMetaData = classDoc.getClassMetaData();
+        String superClassName = classMetaData.getSuperClassName();
+        if (superClassName != null) {
+            // Assume this is a class and so has implemented all properties and methods somewhere in the superclass hierarchy
+            ClassDoc superClass = model.getClassDoc(superClassName);
+            classDoc.setSuperClass(superClass);
+            return;
+        }
+
+        // Assume this is an interface - pick one interface to be the supertype
+        // TODO - improve the property and methods builders to handle stuff inherited from multiple interfaces
+
+        List<String> interfaceNames = classMetaData.getInterfaceNames();
+        List<ClassDoc> candidates = new ArrayList<ClassDoc>();
+        for (String interfaceName : interfaceNames) {
+            ClassDoc superInterface = model.findClassDoc(interfaceName);
+            if (superInterface != null) {
+                candidates.add(superInterface);
+            }
+        }
+        if (candidates.isEmpty()) {
+            // No documented supertypes
+            return;
+        }
+
+        ClassDoc superInterface = candidates.get(0);
+        if (candidates.size() > 1) {
+            listener.warning("Ignoring properties and methods inherited from interfaces " + candidates.subList(1, candidates.size()));
+        }
+        classDoc.setSuperClass(superInterface);
+    }
+}
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
deleted file mode 100644
index a9bbb8d..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassExtensionDoc.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.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 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, ClassMetaData targetClass) {
-        this.pluginId = pluginId
-        this.targetClass = targetClass
-    }
-
-    String getPluginId() {
-        return pluginId
-    }
-
-    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() {
-        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 mixinClasses.inject([]) {list, eClass -> eClass.classMethods.inject(list) {x, method -> x << method } }.sort { it.name }
-    }
-
-    List<BlockDoc> getExtensionBlocks() {
-        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/DocBookBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocBookBuilder.java
new file mode 100644
index 0000000..e5ae1db
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocBookBuilder.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.build.docs.dsl.docbook;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+class DocBookBuilder {
+    final LinkedList<Element> stack = new LinkedList<Element>();
+    final Document document;
+
+    DocBookBuilder(Document document) {
+        this.document = document;
+        stack.addFirst(document.createElement("root"));
+    }
+
+    List<Element> getElements() {
+        List<Element> elements = new ArrayList<Element>();
+        for (Node node = stack.getLast().getFirstChild(); node != null; node = node.getNextSibling()) {
+            elements.add((Element) node);
+        }
+        return elements;
+    }
+
+    public void appendChild(String text) {
+        appendChild(document.createTextNode(text));
+    }
+
+    public void appendChild(Node node) {
+        boolean inPara = false;
+        if (node instanceof Element) {
+            Element element = (Element) node;
+            if (element.getTagName().equals("para") && stack.getFirst().getTagName().equals("para")) {
+                pop();
+                inPara = true;
+            }
+        }
+        stack.getFirst().appendChild(node);
+        if (inPara) {
+            Element para = document.createElement("para");
+            push(para);
+        }
+    }
+
+    public void push(Element element) {
+        stack.getFirst().appendChild(element);
+        stack.addFirst(element);
+    }
+
+    public Element pop() {
+        Element element = stack.removeFirst();
+        if (emptyPara(element)) {
+            element.getParentNode().removeChild(element);
+        }
+        return element;
+    }
+
+    private boolean emptyPara(Element element) {
+        if (!element.getTagName().equals("para")) {
+            return false;
+        }
+        for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
+            if (!(node instanceof Text)) {
+                return false;
+            }
+            Text text = (Text) node;
+            if (!text.getTextContent().matches("\\s*")) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocComment.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocComment.java
index 83baff4..a93bc55 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocComment.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DocComment.java
@@ -15,8 +15,10 @@
  */
 package org.gradle.build.docs.dsl.docbook;
 
-import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+
+import java.util.List;
 
 public interface DocComment {
-    Iterable<? extends Node> getDocbook();
+    List<Element> getDocbook();
 }
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 eddff84..7146a7b 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,11 +16,12 @@
 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.gradle.build.docs.dsl.source.TypeNameResolver
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionMetaData
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.w3c.dom.Document
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
 
 class DslDocModel {
     private final File classDocbookDir
@@ -29,6 +30,8 @@ class DslDocModel {
     private final ClassMetaDataRepository<ClassMetaData> classMetaData
     private final Map<String, ClassExtensionMetaData> extensionMetaData
     private final JavadocConverter javadocConverter
+    private final ClassDocBuilder docBuilder
+    private final LinkedList<String> currentlyBuilding = new LinkedList<String>()
 
     DslDocModel(File classDocbookDir, Document document, ClassMetaDataRepository<ClassMetaData> classMetaData, Map<String, ClassExtensionMetaData> extensionMetaData) {
         this.classDocbookDir = classDocbookDir
@@ -36,46 +39,73 @@ class DslDocModel {
         this.classMetaData = classMetaData
         this.extensionMetaData = extensionMetaData
         javadocConverter = new JavadocConverter(document, new JavadocLinkConverter(document, new TypeNameResolver(classMetaData), new LinkRenderer(document, this), classMetaData))
+        docBuilder = new ClassDocBuilder(this, javadocConverter)
+    }
+
+    Collection<ClassDoc> getClasses() {
+        return classes.values().findAll { !it.name.contains('.internal.') }
     }
 
     boolean isKnownType(String className) {
         return classMetaData.find(className) != null
     }
 
+    ClassDoc findClassDoc(String className) {
+        ClassDoc classDoc = classes[className]
+        if (classDoc == null && getFileForClass(className).isFile()) {
+            return getClassDoc(className)
+        }
+        return classDoc
+    }
+
     ClassDoc getClassDoc(String className) {
         ClassDoc classDoc = classes[className]
         if (classDoc == null) {
             classDoc = loadClassDoc(className)
             classes[className] = classDoc
+            new ReferencedTypeBuilder(this).build(classDoc)
         }
         return classDoc
     }
 
     private ClassDoc loadClassDoc(String className) {
-        ClassMetaData classMetaData = classMetaData.find(className)
-        if (!classMetaData) {
-            if (!className.contains('.internal.')) {
-                throw new RuntimeException("No meta-data found for class '$className'.")
-            }
-            classMetaData = new ClassMetaData(className)
+        if (currentlyBuilding.contains(className)) {
+            throw new RuntimeException("Cycle building $className. Currently building $currentlyBuilding")
         }
+        currentlyBuilding.addLast(className)
         try {
-            ClassExtensionMetaData extensionMetaData = extensionMetaData[className]
-            if (!extensionMetaData) {
-                extensionMetaData = new ClassExtensionMetaData(className)
+            ClassMetaData classMetaData = classMetaData.find(className)
+            if (!classMetaData) {
+                if (!className.contains('.internal.')) {
+                    throw new RuntimeException("No meta-data found for class '$className'.")
+                }
+                classMetaData = new ClassMetaData(className)
             }
-            File classFile = new File(classDocbookDir, "${className}.xml")
-            if (!classFile.isFile()) {
-                throw new RuntimeException("Docbook source file not found for class '$className' in $classDocbookDir.")
+            try {
+                ClassExtensionMetaData extensionMetaData = extensionMetaData[className]
+                if (!extensionMetaData) {
+                    extensionMetaData = new ClassExtensionMetaData(className)
+                }
+                File classFile = getFileForClass(className)
+                if (!classFile.isFile()) {
+                    throw new RuntimeException("Docbook source file not found for class '$className' in $classDocbookDir.")
+                }
+                XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider()
+                def doc = new ClassDoc(className, provider.parse(classFile), document, classMetaData, extensionMetaData)
+                docBuilder.build(doc)
+                return doc
+            } catch (ClassDocGenerationException e) {
+                throw e
+            } catch (Exception e) {
+                throw new ClassDocGenerationException("Could not load the class documentation for class '$className'.", e)
             }
-            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 ClassDocGenerationException("Could not load the class documentation for class '$className'.", e)
+        } finally {
+            currentlyBuilding.removeLast()
         }
     }
+
+    private File getFileForClass(String className) {
+        File classFile = new File(classDocbookDir, "${className}.xml")
+        classFile
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java
deleted file mode 100644
index f40cb55..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.w3c.dom.Element;
-
-import java.util.List;
-
-public interface DslElementDoc {
-    String getId();
-
-    Element getDescription();
-
-    List<Element> getComment();
-
-    boolean isDeprecated();
-
-    boolean isExperimental();
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java
new file mode 100644
index 0000000..f06f991
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.DslElementDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ElementWarningsRenderer {
+    public void renderTo(DslElementDoc elementDoc, String type, Element parent) {
+        if (elementDoc.isDeprecated()) {
+            Document document = parent.getOwnerDocument();
+            Element caution = document.createElement("caution");
+            parent.appendChild(caution);
+            Element para = document.createElement("para");
+            caution.appendChild(para);
+            para.appendChild(document.createTextNode(String.format("Note: This %s is ", type)));
+            Element link = document.createElement("ulink");
+            para.appendChild(link);
+            link.setAttribute("url", "../userguide/feature_lifecycle.html");
+            link.appendChild(document.createTextNode("deprecated"));
+            para.appendChild(document.createTextNode(" and will be removed in the next major version of Gradle."));
+        }
+        if (elementDoc.isIncubating()) {
+            Document document = parent.getOwnerDocument();
+            Element caution = document.createElement("caution");
+            parent.appendChild(caution);
+            Element para = document.createElement("para");
+            caution.appendChild(para);
+            para.appendChild(document.createTextNode(String.format("Note: This %s is ", type)));
+            Element link = document.createElement("ulink");
+            para.appendChild(link);
+            link.setAttribute("url", "../userguide/feature_lifecycle.html");
+            link.appendChild(document.createTextNode("incubating"));
+            para.appendChild(document.createTextNode(" and may change in a future version of Gradle."));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionBlocksSummaryRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionBlocksSummaryRenderer.java
new file mode 100644
index 0000000..829a2e6
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionBlocksSummaryRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ExtensionBlocksSummaryRenderer {
+    private final BlockTableRenderer blockTableRenderer;
+
+    public ExtensionBlocksSummaryRenderer(BlockTableRenderer blockTableRenderer) {
+        this.blockTableRenderer = blockTableRenderer;
+    }
+
+    public void renderTo(ClassExtensionDoc extension, Element parent) {
+        if (extension.getExtensionBlocks().isEmpty()) {
+            return;
+        }
+
+        Document document = parent.getOwnerDocument();
+
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        title.appendChild(document.createTextNode("Script blocks added by the "));
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        Element titleabbrev = document.createElement("titleabbrev");
+        section.appendChild(titleabbrev);
+        literal = document.createElement("literal");
+        titleabbrev.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        titleabbrev.appendChild(document.createTextNode(" plugin"));
+
+        Element table = document.createElement("table");
+        section.appendChild(table);
+
+        title = document.createElement("title");
+        table.appendChild(title);
+        title.appendChild(document.createTextNode("Script blocks - "));
+        literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        blockTableRenderer.renderTo(extension.getExtensionBlocks(), table);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMethodsSummaryRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMethodsSummaryRenderer.java
new file mode 100644
index 0000000..3e509a9
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMethodsSummaryRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ExtensionMethodsSummaryRenderer {
+    private final MethodTableRenderer methodTableRenderer;
+
+    public ExtensionMethodsSummaryRenderer(MethodTableRenderer methodTableRenderer) {
+        this.methodTableRenderer = methodTableRenderer;
+    }
+
+    public void renderTo(ClassExtensionDoc extension, Element parent) {
+        if (extension.getExtensionMethods().isEmpty()) {
+            return;
+        }
+
+        Document document = parent.getOwnerDocument();
+
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        title.appendChild(document.createTextNode("Methods added by the "));
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        Element titleabbrev = document.createElement("titleabbrev");
+        section.appendChild(titleabbrev);
+        literal = document.createElement("literal");
+        titleabbrev.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        titleabbrev.appendChild(document.createTextNode(" plugin"));
+
+        Element table = document.createElement("table");
+        section.appendChild(table);
+
+        title = document.createElement("title");
+        table.appendChild(title);
+        title.appendChild(document.createTextNode("Methods - "));
+        literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        methodTableRenderer.renderTo(extension.getExtensionMethods(), table);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionPropertiesSummaryRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionPropertiesSummaryRenderer.java
new file mode 100644
index 0000000..f43397e
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionPropertiesSummaryRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class ExtensionPropertiesSummaryRenderer {
+    private final PropertyTableRenderer propertyTableRenderer;
+
+    public ExtensionPropertiesSummaryRenderer(PropertyTableRenderer propertyTableRenderer) {
+        this.propertyTableRenderer = propertyTableRenderer;
+    }
+
+    public void renderTo(ClassExtensionDoc extension, Element parent) {
+        if (extension.getExtensionProperties().isEmpty()) {
+            return;
+        }
+
+        Document document = parent.getOwnerDocument();
+
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        title.appendChild(document.createTextNode("Properties added by the "));
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        Element titleabbrev = document.createElement("titleabbrev");
+        section.appendChild(titleabbrev);
+        literal = document.createElement("literal");
+        titleabbrev.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        titleabbrev.appendChild(document.createTextNode(" plugin"));
+
+        Element table = document.createElement("table");
+        section.appendChild(table);
+
+        title = document.createElement("title");
+        table.appendChild(title);
+        title.appendChild(document.createTextNode("Properties - "));
+        literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(extension.getPluginId()));
+        title.appendChild(document.createTextNode(" plugin"));
+
+        propertyTableRenderer.renderTo(extension.getExtensionProperties(), table);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtraAttributeDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtraAttributeDoc.groovy
deleted file mode 100644
index c91cdd2..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtraAttributeDoc.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.build.docs.dsl.docbook
-
-import org.w3c.dom.Element
-
-class ExtraAttributeDoc {
-    private final Element titleCell
-    private final Element valueCell
-
-    ExtraAttributeDoc(Element titleCell, Element valueCell) {
-        this.titleCell = titleCell
-        this.valueCell = valueCell
-    }
-
-    @Override
-    String toString() {
-        return "attribute[key: $key, value: $valueCell.textContent]"
-    }
-
-    String getKey() {
-        return titleCell.textContent
-    }
-
-    List<Node> getTitle() {
-        return titleCell.childNodes.collect { it }
-    }
-
-    List<Node> getValue() {
-        return valueCell.childNodes.collect { it }
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexer.java
index 68dbda0..aa84909 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexer.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexer.java
@@ -17,10 +17,13 @@ package org.gradle.build.docs.dsl.docbook;
 
 import java.util.*;
 
+/**
+ * Normalises and cleans up HTML to convert it to XML semantics.
+ */
 public class HtmlToXmlJavadocLexer implements JavadocLexer {
     private final JavadocLexer lexer;
-    private final LinkedList<String> elementStack = new LinkedList<String>();
     private final Set<String> blockElements = new HashSet<String>();
+    private final Set<String> blockContent = new HashSet<String>();
 
     public HtmlToXmlJavadocLexer(JavadocLexer lexer) {
         this.lexer = lexer;
@@ -35,69 +38,134 @@ public class HtmlToXmlJavadocLexer implements JavadocLexer {
         blockElements.add("h4");
         blockElements.add("h5");
         blockElements.add("h6");
+        blockElements.add("table");
+        blockElements.add("thead");
+        blockElements.add("tbody");
+        blockElements.add("tr");
+        blockElements.add("dl");
+        blockElements.add("dt");
+        blockElements.add("dd");
+
+        blockContent.add("ul");
+        blockContent.add("ol");
+        blockContent.add("table");
+        blockContent.add("thead");
+        blockContent.add("tbody");
+        blockContent.add("tr");
+        blockContent.add("dl");
     }
 
     public void visit(TokenVisitor visitor) {
         lexer.visit(new VisitorImpl(visitor));
     }
 
-    private void unwindTo(String element, TokenVisitor visitor) {
-        if (elementStack.contains(element)) {
-            while (!elementStack.getFirst().equals(element)) {
-                visitor.onEndHtmlElement(elementStack.removeFirst());
-            }
-            elementStack.removeFirst();
-            visitor.onEndHtmlElement(element);
-        }
-    }
-
     private class VisitorImpl extends TokenVisitor {
         private final TokenVisitor visitor;
+        private final LinkedList<String> elementStack = new LinkedList<String>();
+        private final Map<String, String> attributes = new HashMap<String, String>();
 
         public VisitorImpl(TokenVisitor visitor) {
             this.visitor = visitor;
         }
 
+        private void unwindTo(String element, TokenVisitor visitor) {
+            if (elementStack.contains(element)) {
+                while (!elementStack.getFirst().equals(element)) {
+                    visitor.onEndHtmlElement(elementStack.removeFirst());
+                }
+                elementStack.removeFirst();
+                visitor.onEndHtmlElement(element);
+            }
+        }
+
+        private void unwindTo(Collection<String> ancestors, TokenVisitor visitor) {
+            for (int i = 0; i < elementStack.size(); i++) {
+                if (ancestors.contains(elementStack.get(i))) {
+                    for (; i > 0; i--) {
+                        visitor.onEndHtmlElement(elementStack.removeFirst());
+                    }
+                    break;
+                }
+            }
+        }
+
         @Override
         public void onStartHtmlElement(String name) {
-            if (name.equals("li")) {
-                unwindTo("li", visitor);
-            } else if (blockElements.contains(name)) {
-                unwindTo("p", visitor);
-            }
-            elementStack.addFirst(name);
-            visitor.onStartHtmlElement(name);
+            attributes.clear();
         }
 
         @Override
         public void onHtmlElementAttribute(String name, String value) {
-            visitor.onHtmlElementAttribute(name, value);
+            attributes.put(name, value);
         }
 
         @Override
         public void onStartHtmlElementComplete(String name) {
+            if (name.equals("li")) {
+                unwindTo(Arrays.asList("ul", "ol"), visitor);
+            } else if (name.equals("dt") || name.endsWith("dd")) {
+                unwindTo(Arrays.asList("dl"), visitor);
+            } else if (name.equals("tr")) {
+                unwindTo(Arrays.asList("table", "thead", "tbody"), visitor);
+            } else if (name.equals("th") || name.endsWith("td")) {
+                unwindTo(Arrays.asList("tr", "table", "thead", "tbody"), visitor);
+            } else if (blockElements.contains(name)) {
+                unwindTo("p", visitor);
+            } else if (!blockContent.contains(name) && !(name.equals("a") && attributes.containsKey("name"))) {
+                onInlineContent();
+            }
+
+            elementStack.addFirst(name);
+            visitor.onStartHtmlElement(name);
+            for (Map.Entry<String, String> entry : attributes.entrySet()) {
+                visitor.onHtmlElementAttribute(entry.getKey(), entry.getValue());
+            }
+            attributes.clear();
             visitor.onStartHtmlElementComplete(name);
         }
 
         @Override
         public void onEndHtmlElement(String name) {
             unwindTo(name, visitor);
-            visitor.onEndHtmlElement(name);
+        }
+
+        private void onInlineContent() {
+            if (elementStack.isEmpty() || blockContent.contains(elementStack.getFirst())) {
+                elementStack.addFirst("p");
+                visitor.onStartHtmlElement("p");
+                visitor.onStartHtmlElementComplete("p");
+            }
         }
 
         @Override
         public void onStartJavadocTag(String name) {
+            onInlineContent();
             visitor.onStartJavadocTag(name);
         }
 
         @Override
         public void onEndJavadocTag(String name) {
+            onInlineContent();
             visitor.onEndJavadocTag(name);
         }
 
         @Override
         public void onText(String text) {
-            visitor.onText(text);
+            boolean ws = text.matches("\\s*");
+            if (!ws) {
+                onInlineContent();
+                visitor.onText(text);
+            } else if (!elementStack.isEmpty() && !blockContent.contains(elementStack.getFirst())) {
+                visitor.onText(text);
+            }
+        }
+
+        @Override
+        void onEnd() {
+            while (!elementStack.isEmpty()) {
+                visitor.onEndHtmlElement(elementStack.removeFirst());
+            }
+            visitor.onEnd();
         }
     }
 }
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 22464d3..5685f02 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
@@ -16,9 +16,9 @@
 package org.gradle.build.docs.dsl.docbook;
 
 import org.gradle.api.GradleException;
-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.source.model.ClassMetaData;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -114,7 +114,7 @@ public class JavadocConverter {
     private DocCommentImpl parse(String rawCommentText, ClassMetaData classMetaData,
                                  CommentSource inheritedCommentSource, GenerationListener listener) {
         JavadocLexer lexer = new HtmlToXmlJavadocLexer(new BasicJavadocLexer(new JavadocScanner(rawCommentText)));
-        NodeStack nodes = new NodeStack(document);
+        DocBookBuilder nodes = new DocBookBuilder(document);
         final HtmlGeneratingTokenHandler handler = new HtmlGeneratingTokenHandler(nodes, document);
         handler.add(new HtmlElementTranslatingHandler(nodes, document));
         handler.add(new PreElementHandler(nodes, document));
@@ -122,8 +122,9 @@ public class JavadocConverter {
         handler.add(new HeaderHandler(nodes, document));
         handler.add(new LinkHandler(nodes, linkConverter, classMetaData, listener));
         handler.add(new InheritDocHandler(nodes, inheritedCommentSource));
-        handler.add(new ValueHtmlElementHandler(nodes, linkConverter, classMetaData, listener));
+        handler.add(new ValueTagHandler(nodes, linkConverter, classMetaData, listener));
         handler.add(new TableHandler(nodes, document));
+        handler.add(new DlElementHandler(nodes, document));
         handler.add(new AnchorElementHandler(nodes, document, classMetaData));
         handler.add(new AToLinkTranslatingHandler(nodes, document, classMetaData));
         handler.add(new AToUlinkTranslatingHandler(nodes, document));
@@ -132,8 +133,7 @@ public class JavadocConverter {
 
         lexer.visit(handler);
 
-        nodes.complete();
-        return new DocCommentImpl(nodes.nodes);
+        return new DocCommentImpl(nodes.getElements());
     }
 
     private static class DocCommentImpl implements DocComment {
@@ -148,121 +148,8 @@ public class JavadocConverter {
         }
     }
 
-    private static class NodeStack {
-        final Set<String> blockElements = new HashSet<String>();
-        final List<Element> nodes = new ArrayList<Element>();
-        final LinkedList<Element> stack = new LinkedList<Element>();
-        final LinkedList<String> tags = new LinkedList<String>();
-        final Document document;
-
-        private NodeStack(Document document) {
-            this.document = document;
-            blockElements.add("para");
-            blockElements.add("section");
-            blockElements.add("title");
-            blockElements.add("programlisting");
-            blockElements.add("itemizedlist");
-            blockElements.add("orderedlist");
-            blockElements.add("listitem");
-            blockElements.add("table");
-            blockElements.add("tr");
-            blockElements.add("td");
-            blockElements.add("thead");
-        }
-
-        public void appendChild(String text) {
-            if (stack.isEmpty() && text.trim().length() == 0) {
-                return;
-            }
-            appendChild(document.createTextNode(text));
-        }
-
-        public void appendChild(Node node) {
-            boolean blockElement = node instanceof Element && blockElements.contains(node.getNodeName());
-            boolean inlineNode = !blockElement && !(node instanceof Element && node.getNodeName().equals("anchor"));
-            if (blockElement) {
-                endCurrentPara();
-            }
-            if (stack.isEmpty()) {
-                if (!inlineNode) {
-                    appendToResult((Element) node);
-                } else {
-                    Element wrapper = document.createElement("para");
-                    wrapper.appendChild(node);
-                    stack.addFirst(wrapper);
-                    tags.addFirst("");
-                }
-            } else {
-                stack.getFirst().appendChild(node);
-            }
-        }
-
-        public void push(String tag, Element element) {
-            boolean blockElement = blockElements.contains(element.getNodeName());
-            if (blockElement) {
-                endCurrentPara();
-            }
-            if (stack.isEmpty()) {
-                if (blockElement) {
-                    stack.addFirst(element);
-                    tags.addFirst(tag);
-                } else {
-                    Element wrapper = document.createElement("para");
-                    wrapper.appendChild(element);
-                    stack.addFirst(wrapper);
-                    tags.addFirst("");
-                    stack.addFirst(element);
-                    tags.addFirst(tag);
-                }
-            } else {
-                stack.getFirst().appendChild(element);
-                stack.addFirst(element);
-                tags.addFirst(tag);
-            }
-        }
-
-        public Element pop(String tag) {
-            Element element = null;
-            if (!tags.isEmpty() && tags.getFirst().equals(tag)) {
-                element = stack.removeFirst();
-                tags.removeFirst();
-                if (stack.isEmpty()) {
-                    appendToResult(element);
-                }
-            }
-            return element;
-        }
-
-        private void endCurrentPara() {
-            if (stack.isEmpty() || !stack.getFirst().getNodeName().equals("para")) {
-                return;
-            }
-
-            Element para = stack.removeFirst();
-            tags.removeFirst();
-            if (stack.isEmpty()) {
-                appendToResult(para);
-            }
-        }
-
-        private void appendToResult(Element element) {
-            if (element.getFirstChild() == null && element.getAttributes().getLength() == 0) {
-                return;
-            }
-            nodes.add(element);
-        }
-
-        public void complete() {
-            if (!stack.isEmpty()) {
-                appendToResult(stack.getLast());
-            }
-            stack.clear();
-            tags.clear();
-        }
-    }
-
     private static class HtmlGeneratingTokenHandler extends JavadocLexer.TokenVisitor {
-        final NodeStack nodes;
+        final DocBookBuilder nodes;
         final List<HtmlElementHandler> elementHandlers = new ArrayList<HtmlElementHandler>();
         final List<JavadocTagHandler> tagHandlers = new ArrayList<JavadocTagHandler>();
         final LinkedList<HtmlElementHandler> handlerStack = new LinkedList<HtmlElementHandler>();
@@ -271,7 +158,7 @@ public class JavadocConverter {
         StringBuilder tagValue;
         final Document document;
 
-        public HtmlGeneratingTokenHandler(NodeStack nodes, Document document) {
+        public HtmlGeneratingTokenHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
         }
@@ -358,11 +245,11 @@ public class JavadocConverter {
     }
 
     private static class UnknownJavadocTagHandler implements JavadocTagHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final GenerationListener listener;
 
-        private UnknownJavadocTagHandler(NodeStack nodes, Document document, GenerationListener listener) {
+        private UnknownJavadocTagHandler(DocBookBuilder nodes, Document document, GenerationListener listener) {
             this.nodes = nodes;
             this.document = document;
             this.listener = listener;
@@ -378,11 +265,11 @@ public class JavadocConverter {
     }
 
     private static class UnknownHtmlElementHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final GenerationListener listener;
 
-        private UnknownHtmlElementHandler(NodeStack nodes, Document document, GenerationListener listener) {
+        private UnknownHtmlElementHandler(DocBookBuilder nodes, Document document, GenerationListener listener) {
             this.nodes = nodes;
             this.document = document;
             this.listener = listener;
@@ -392,7 +279,7 @@ public class JavadocConverter {
             listener.warning(String.format("Unsupported HTML element <%s>", elementName));
             Element element = document.createElement("UNHANDLED-ELEMENT");
             element.appendChild(document.createTextNode(String.format("<%s>", elementName)));
-            nodes.push(elementName, element);
+            nodes.push(element);
             return true;
         }
 
@@ -402,16 +289,16 @@ public class JavadocConverter {
 
         public void onEndElement(String elementName) {
             nodes.appendChild(String.format("</%s>", elementName));
-            nodes.pop(elementName);
+            nodes.pop();
         }
     }
 
     private static class JavadocTagToElementTranslatingHandler implements JavadocTagHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final Map<String, String> tagToElementMap = new HashMap<String, String>();
 
-        private JavadocTagToElementTranslatingHandler(NodeStack nodes, Document document) {
+        private JavadocTagToElementTranslatingHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
             tagToElementMap.put("code", "literal");
@@ -430,11 +317,11 @@ public class JavadocConverter {
     }
 
     private static class HtmlElementTranslatingHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final Map<String, String> elementToElementMap = new HashMap<String, String>();
 
-        private HtmlElementTranslatingHandler(NodeStack nodes, Document document) {
+        private HtmlElementTranslatingHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
             elementToElementMap.put("p", "para");
@@ -453,7 +340,7 @@ public class JavadocConverter {
             if (newElementName == null) {
                 return false;
             }
-            nodes.push(element, document.createElement(newElementName));
+            nodes.push(document.createElement(newElementName));
             return true;
         }
 
@@ -462,15 +349,15 @@ public class JavadocConverter {
         }
 
         public void onEndElement(String element) {
-            nodes.pop(element);
+            nodes.pop();
         }
     }
 
     private static class PreElementHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
 
-        private PreElementHandler(NodeStack nodes, Document document) {
+        private PreElementHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
         }
@@ -484,7 +371,7 @@ public class JavadocConverter {
             //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);
+            nodes.push(newElement);
             return true;
         }
 
@@ -493,16 +380,16 @@ public class JavadocConverter {
         }
 
         public void onEndElement(String element) {
-            nodes.pop(element);
+            nodes.pop();
         }
     }
 
     private static class HeaderHandler implements HtmlElementHandler {
-        final NodeStack nodes;
+        final DocBookBuilder nodes;
         final Document document;
         int sectionDepth;
 
-        private HeaderHandler(NodeStack nodes, Document document) {
+        private HeaderHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
         }
@@ -517,15 +404,15 @@ public class JavadocConverter {
                 sectionDepth = depth - 1;
             }
             while (sectionDepth >= depth) {
-                nodes.pop("section");
+                nodes.pop();
                 sectionDepth--;
             }
             Element section = document.createElement("section");
             while (sectionDepth < depth) {
-                nodes.push("section", section);
+                nodes.push(section);
                 sectionDepth++;
             }
-            nodes.push("title", document.createElement("title"));
+            nodes.push(document.createElement("title"));
             sectionDepth = depth;
             return true;
         }
@@ -535,18 +422,18 @@ public class JavadocConverter {
         }
 
         public void onEndElement(String element) {
-            nodes.pop("title");
+            nodes.pop();
         }
     }
 
     private static class TableHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private Element currentTable;
         private Element currentRow;
         private Element header;
 
-        public TableHandler(NodeStack nodes, Document document) {
+        public TableHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
         }
@@ -557,12 +444,12 @@ public class JavadocConverter {
                     throw new UnsupportedOperationException("A table within a table is not supported.");
                 }
                 currentTable = document.createElement("table");
-                nodes.push(elementName, currentTable);
+                nodes.push(currentTable);
                 return true;
             }
             if (elementName.equals("tr")) {
                 currentRow = document.createElement("tr");
-                nodes.push(elementName, currentRow);
+                nodes.push(currentRow);
                 return true;
             }
             if (elementName.equals("th")) {
@@ -571,11 +458,11 @@ public class JavadocConverter {
                     currentTable.insertBefore(header, null);
                     header.appendChild(currentRow);
                 }
-                nodes.push(elementName, document.createElement("td"));
+                nodes.push(document.createElement("td"));
                 return true;
             }
             if (elementName.equals("td")) {
-                nodes.push(elementName, document.createElement("td"));
+                nodes.push(document.createElement("td"));
                 return true;
             }
             return false;
@@ -589,7 +476,7 @@ public class JavadocConverter {
             if (elementName.equals("tr")) {
                 currentRow = null;
             }
-            nodes.pop(elementName);
+            nodes.pop();
         }
 
         public void onText(String text) {
@@ -598,11 +485,11 @@ public class JavadocConverter {
     }
 
     private static class AnchorElementHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final ClassMetaData classMetaData;
 
-        private AnchorElementHandler(NodeStack nodes, Document document, ClassMetaData classMetaData) {
+        private AnchorElementHandler(DocBookBuilder nodes, Document document, ClassMetaData classMetaData) {
             this.nodes = nodes;
             this.document = document;
             this.classMetaData = classMetaData;
@@ -627,11 +514,11 @@ public class JavadocConverter {
     }
 
     private static class AToLinkTranslatingHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
         private final ClassMetaData classMetaData;
 
-        private AToLinkTranslatingHandler(NodeStack nodes, Document document, ClassMetaData classMetaData) {
+        private AToLinkTranslatingHandler(DocBookBuilder nodes, Document document, ClassMetaData classMetaData) {
             this.nodes = nodes;
             this.document = document;
             this.classMetaData = classMetaData;
@@ -648,12 +535,12 @@ public class JavadocConverter {
             Element element = document.createElement("link");
             String targetId = String.format("%s.%s", classMetaData.getClassName(), href.substring(1));
             element.setAttribute("linkend", targetId);
-            nodes.push(elementName, element);
+            nodes.push(element);
             return true;
         }
 
         public void onEndElement(String element) {
-            nodes.pop(element);
+            nodes.pop();
         }
 
         public void onText(String text) {
@@ -662,10 +549,10 @@ public class JavadocConverter {
     }
 
     private static class AToUlinkTranslatingHandler implements HtmlElementHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final Document document;
 
-        private AToUlinkTranslatingHandler(NodeStack nodes, Document document) {
+        private AToUlinkTranslatingHandler(DocBookBuilder nodes, Document document) {
             this.nodes = nodes;
             this.document = document;
         }
@@ -680,12 +567,74 @@ public class JavadocConverter {
             }
             Element element = document.createElement("ulink");
             element.setAttribute("url", href);
-            nodes.push(elementName, element);
+            nodes.push(element);
             return true;
         }
 
         public void onEndElement(String element) {
-            nodes.pop(element);
+            nodes.pop();
+        }
+
+        public void onText(String text) {
+            nodes.appendChild(text);
+        }
+    }
+
+    private static class DlElementHandler implements HtmlElementHandler {
+        private final DocBookBuilder nodes;
+        private final Document document;
+        private Element currentList;
+        private Element currentItem;
+
+        public DlElementHandler(DocBookBuilder nodes, Document document) {
+            this.nodes = nodes;
+            this.document = document;
+        }
+
+        public boolean onStartElement(String elementName, Map<String, String> attributes) {
+            if (elementName.equals("dl")) {
+                if (currentList != null) {
+                    throw new UnsupportedOperationException("<dl> within a <dl> is not supported.");
+                }
+                currentList = document.createElement("variablelist");
+                nodes.push(currentList);
+                return true;
+            }
+            if (elementName.equals("dt")) {
+                if (currentItem != null) {
+                    nodes.pop();
+                }
+                currentItem = document.createElement("varlistentry");
+                nodes.push(currentItem);
+                nodes.push(document.createElement("term"));
+                return true;
+            }
+            if (elementName.equals("dd")) {
+                if (currentItem == null) {
+                    throw new IllegalStateException("No <dt> element preceeding <dd> element.");
+                }
+                nodes.push(document.createElement("listitem"));
+                return true;
+            }
+
+            return false;
+        }
+
+        public void onEndElement(String element) {
+            if (element.equals("dl")) {
+                currentList = null;
+                if (currentItem != null) {
+                    currentItem = null;
+                    nodes.pop();
+                }
+                nodes.pop();
+            }
+            if (element.equals("dt")) {
+                nodes.pop();
+            }
+            if (element.equals("dd")) {
+                nodes.pop();
+            }
         }
 
         public void onText(String text) {
@@ -693,14 +642,14 @@ public class JavadocConverter {
         }
     }
 
-    private static class ValueHtmlElementHandler implements JavadocTagHandler {
+    private static class ValueTagHandler implements JavadocTagHandler {
         private final JavadocLinkConverter linkConverter;
         private final ClassMetaData classMetaData;
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final GenerationListener listener;
 
-        public ValueHtmlElementHandler(NodeStack nodes, JavadocLinkConverter linkConverter, ClassMetaData classMetaData,
-                                       GenerationListener listener) {
+        public ValueTagHandler(DocBookBuilder nodes, JavadocLinkConverter linkConverter, ClassMetaData classMetaData,
+                               GenerationListener listener) {
             this.nodes = nodes;
             this.linkConverter = linkConverter;
             this.classMetaData = classMetaData;
@@ -717,12 +666,12 @@ public class JavadocConverter {
     }
 
     private static class LinkHandler implements JavadocTagHandler {
-        private final NodeStack nodes;
+        private final DocBookBuilder nodes;
         private final JavadocLinkConverter linkConverter;
         private final ClassMetaData classMetaData;
         private final GenerationListener listener;
 
-        private LinkHandler(NodeStack nodes, JavadocLinkConverter linkConverter, ClassMetaData classMetaData,
+        private LinkHandler(DocBookBuilder nodes, JavadocLinkConverter linkConverter, ClassMetaData classMetaData,
                             GenerationListener listener) {
             this.nodes = nodes;
             this.linkConverter = linkConverter;
@@ -741,9 +690,9 @@ public class JavadocConverter {
 
     private static class InheritDocHandler implements JavadocTagHandler {
         private final CommentSource source;
-        private final NodeStack nodeStack;
+        private final DocBookBuilder nodeStack;
 
-        private InheritDocHandler(NodeStack nodeStack, CommentSource source) {
+        private InheritDocHandler(DocBookBuilder nodeStack, CommentSource source) {
             this.nodeStack = nodeStack;
             this.source = source;
         }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLexer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLexer.java
index 05a623d..4846329 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLexer.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLexer.java
@@ -42,6 +42,9 @@ public interface JavadocLexer {
 
         void onText(String text) {
         }
+
+        void onEnd() {
+        }
     }
 
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
index fb14df8..e20e8e1 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.build.docs.dsl.docbook;
 
-import org.gradle.build.docs.dsl.TypeNameResolver;
-import org.gradle.build.docs.dsl.model.ClassMetaData;
-import org.gradle.build.docs.dsl.model.MethodMetaData;
-import org.gradle.build.docs.dsl.model.TypeMetaData;
+import org.gradle.build.docs.dsl.source.TypeNameResolver;
+import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.dsl.source.model.TypeMetaData;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
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 56f0b99..5b8f2da 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
@@ -16,8 +16,8 @@
 package org.gradle.build.docs.dsl.docbook;
 
 import org.apache.commons.lang.StringUtils;
-import org.gradle.build.docs.dsl.model.MethodMetaData;
-import org.gradle.build.docs.dsl.model.TypeMetaData;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.dsl.source.model.TypeMetaData;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDetailRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDetailRenderer.java
new file mode 100644
index 0000000..7aa1271
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDetailRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc;
+import org.gradle.build.docs.dsl.source.model.ParameterMetaData;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class MethodDetailRenderer {
+    private final GenerationListener listener;
+    private final LinkRenderer linkRenderer;
+    private final ElementWarningsRenderer warningsRenderer = new ElementWarningsRenderer();
+
+    public MethodDetailRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        this.linkRenderer = linkRenderer;
+        this.listener = listener;
+    }
+
+    public void renderTo(MethodDoc methodDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+        section.setAttribute("id", methodDoc.getId());
+        section.setAttribute("role", "detail");
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        title.appendChild(linkRenderer.link(methodDoc.getMetaData().getReturnType(), listener));
+        title.appendChild(document.createTextNode(" "));
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(methodDoc.getName()));
+        title.appendChild(document.createTextNode("("));
+        List<ParameterMetaData> parameters = methodDoc.getMetaData().getParameters();
+        for (int i = 0; i < parameters.size(); i++) {
+            ParameterMetaData param = parameters.get(i);
+            if (i > 0) {
+                title.appendChild(document.createTextNode(", "));
+            }
+            title.appendChild(linkRenderer.link(param.getType(), listener));
+            title.appendChild(document.createTextNode(" " + param.getName()));
+        }
+        title.appendChild(document.createTextNode(")"));
+
+        warningsRenderer.renderTo(methodDoc, "method", section);
+
+        for (Element element : methodDoc.getComment()) {
+            section.appendChild(document.importNode(element, true));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy
deleted file mode 100644
index 916f29c..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.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.build.docs.dsl.docbook
-
-import org.gradle.build.docs.dsl.model.ClassMetaData
-import org.gradle.build.docs.dsl.model.MethodMetaData
-import org.w3c.dom.Element
-
-class MethodDoc implements DslElementDoc {
-    private final String id
-    private final MethodMetaData metaData
-    private final List<Element> comment
-
-    MethodDoc(MethodMetaData metaData, List<Element> comment) {
-        this(metaData.ownerClass, metaData, comment)
-    }
-
-    MethodDoc(ClassMetaData referringClass, MethodMetaData metaData, List<Element> comment) {
-        this.metaData = metaData
-        id = "$referringClass.className:$metaData.overrideSignature"
-        this.comment = comment
-    }
-
-    MethodDoc forClass(ClassMetaData c) {
-        return new MethodDoc(c, metaData, comment)
-    }
-
-    String getId() {
-        return id
-    }
-
-    String getName() {
-        return metaData.name
-    }
-
-    MethodMetaData getMetaData() {
-        return metaData
-    }
-
-    boolean isDeprecated() {
-        return metaData.deprecated
-    }
-
-    boolean isExperimental() {
-        return metaData.experimental
-    }
-
-    Element getDescription() {
-        return comment.find { it.nodeName == 'para' }
-    }
-
-    List<Element> getComment() {
-        return comment
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodTableRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodTableRenderer.java
new file mode 100644
index 0000000..3c78235
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodTableRenderer.java
@@ -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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class MethodTableRenderer {
+    public void renderTo(Iterable<MethodDoc> methods, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        // <thead>
+        //   <tr>
+        //     <td>Method</td>
+        //     <td>Description</td>
+        //   </tr>
+        // </thead>
+        Element thead = document.createElement("thead");
+        parent.appendChild(thead);
+        Element tr = document.createElement("tr");
+        thead.appendChild(tr);
+        Element td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Method"));
+        td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Description"));
+
+        for (MethodDoc methodDoc : methods) {
+            // <tr>
+            //   <td><literal><link linkend="$id">$name</link>$signature</literal></td>
+            //   <td>$description</td>
+            // </tr>
+            tr = document.createElement("tr");
+            parent.appendChild(tr);
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            Element literal = document.createElement("literal");
+            td.appendChild(literal);
+            Element link = document.createElement("link");
+            literal.appendChild(link);
+            link.setAttribute("linkend", methodDoc.getId());
+            link.appendChild(document.createTextNode(methodDoc.getName()));
+            StringBuilder signature = new StringBuilder();
+            signature.append("(");
+            for (int i = 0; i < methodDoc.getMetaData().getParameters().size(); i++) {
+                if (i > 0) {
+                    signature.append(", ");
+                }
+                signature.append(methodDoc.getMetaData().getParameters().get(i).getName());
+            }
+            signature.append(")");
+            literal.appendChild(document.createTextNode(signature.toString()));
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            if (methodDoc.isDeprecated()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Deprecated"));
+            }
+            if (methodDoc.isIncubating()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Incubating"));
+            }
+            td.appendChild(document.importNode(methodDoc.getDescription(), true));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodsRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodsRenderer.java
new file mode 100644
index 0000000..874d931
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodsRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+
+public class MethodsRenderer {
+    private final MethodTableRenderer methodTableRenderer = new MethodTableRenderer();
+    private final ExtensionMethodsSummaryRenderer extensionMethodsSummaryRenderer;
+    private final MethodDetailRenderer methodDetailRenderer;
+
+    public MethodsRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        methodDetailRenderer = new MethodDetailRenderer(linkRenderer, listener);
+        extensionMethodsSummaryRenderer = new ExtensionMethodsSummaryRenderer(methodTableRenderer);
+    }
+
+    public void renderTo(ClassDoc classDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element summarySection = document.createElement("section");
+        parent.appendChild(summarySection);
+
+        Element title = document.createElement("title");
+        summarySection.appendChild(title);
+        title.appendChild(document.createTextNode("Methods"));
+
+        boolean hasMethods = false;
+        Collection<MethodDoc> classMethods = classDoc.getClassMethods();
+        if (!classMethods.isEmpty()) {
+            hasMethods = true;
+
+            Element table = document.createElement("table");
+            summarySection.appendChild(table);
+
+            title = document.createElement("title");
+            table.appendChild(title);
+            title.appendChild(document.createTextNode("Methods - " + classDoc.getSimpleName()));
+
+            methodTableRenderer.renderTo(classMethods, table);
+        }
+
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            hasMethods |= !extensionDoc.getExtensionMethods().isEmpty();
+            extensionMethodsSummaryRenderer.renderTo(extensionDoc, summarySection);
+        }
+
+        if (!hasMethods) {
+            Element para = document.createElement("para");
+            summarySection.appendChild(para);
+            para.appendChild(document.createTextNode("No methods"));
+            return;
+        }
+
+
+        Element detailsSection = document.createElement("section");
+        parent.appendChild(detailsSection);
+
+        title = document.createElement("title");
+        detailsSection.appendChild(title);
+        title.appendChild(document.createTextNode("Method details"));
+
+        for (MethodDoc methodDoc : classMethods) {
+            methodDetailRenderer.renderTo(methodDoc, detailsSection);
+        }
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            for (MethodDoc methodDoc : extensionDoc.getExtensionMethods()) {
+                methodDetailRenderer.renderTo(methodDoc, detailsSection);
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ModelBuilderSupport.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ModelBuilderSupport.java
new file mode 100644
index 0000000..3b56856
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ModelBuilderSupport.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.build.docs.dsl.docbook;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModelBuilderSupport {
+    protected List<Element> children(Element element, String childName) {
+        List<Element> matches = new ArrayList<Element>();
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node node = childNodes.item(i);
+            if (node instanceof Element) {
+                Element childElement = (Element) node;
+                if (childElement.getTagName().equals(childName)) {
+                    matches.add(childElement);
+                }
+            }
+        }
+        return matches;
+    }
+
+    protected Element getChild(Element element, String childName) {
+        Element child = findChild(element, childName);
+        if (child != null) {
+            return child;
+        }
+        throw new RuntimeException(String.format("No <%s> element found in <%s>", childName, element.getTagName()));
+    }
+
+    protected Element findChild(Element element, String childName) {
+        NodeList childNodes = element.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node node = childNodes.item(i);
+            if (node instanceof Element) {
+                Element childElement = (Element) node;
+                if (childElement.getTagName().equals(childName)) {
+                    return childElement;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertiesRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertiesRenderer.java
new file mode 100644
index 0000000..7378ef8
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertiesRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc;
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.Collection;
+
+public class PropertiesRenderer {
+    private final PropertyTableRenderer propertyTableRenderer = new PropertyTableRenderer();
+    private final ExtensionPropertiesSummaryRenderer extensionPropertiesSummaryRenderer;
+    private final PropertyDetailRenderer propertiesDetailRenderer;
+
+    public PropertiesRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        propertiesDetailRenderer = new PropertyDetailRenderer(linkRenderer, listener);
+        extensionPropertiesSummaryRenderer = new ExtensionPropertiesSummaryRenderer(propertyTableRenderer);
+    }
+
+    public void renderTo(ClassDoc classDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        Element summarySection = document.createElement("section");
+        parent.appendChild(summarySection);
+
+        Element title = document.createElement("title");
+        summarySection.appendChild(title);
+        title.appendChild(document.createTextNode("Properties"));
+
+        boolean hasProperties = false;
+        Collection<PropertyDoc> classProperties = classDoc.getClassProperties();
+        if (!classProperties.isEmpty()) {
+            hasProperties = true;
+
+            Element table = document.createElement("table");
+            summarySection.appendChild(table);
+
+            title = document.createElement("title");
+            table.appendChild(title);
+            title.appendChild(document.createTextNode("Properties - " + classDoc.getSimpleName()));
+
+            propertyTableRenderer.renderTo(classProperties, table);
+        }
+
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            hasProperties |= !extensionDoc.getExtensionProperties().isEmpty();
+            extensionPropertiesSummaryRenderer.renderTo(extensionDoc, summarySection);
+        }
+
+        if (!hasProperties) {
+            Element para = document.createElement("para");
+            summarySection.appendChild(para);
+            para.appendChild(document.createTextNode("No properties"));
+            return;
+        }
+
+
+        Element detailsSection = document.createElement("section");
+        parent.appendChild(detailsSection);
+
+        title = document.createElement("title");
+        detailsSection.appendChild(title);
+        title.appendChild(document.createTextNode("Property details"));
+
+        for (PropertyDoc classProperty : classProperties) {
+            propertiesDetailRenderer.renderTo(classProperty, detailsSection);
+        }
+        for (ClassExtensionDoc extensionDoc : classDoc.getClassExtensions()) {
+            for (PropertyDoc propertyDoc : extensionDoc.getExtensionProperties()) {
+                propertiesDetailRenderer.renderTo(propertyDoc, detailsSection);
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java
new file mode 100644
index 0000000..47737c5
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ExtraAttributeDoc;
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class PropertyDetailRenderer {
+    private final GenerationListener listener;
+    private final LinkRenderer linkRenderer;
+    private final ElementWarningsRenderer warningsRenderer = new ElementWarningsRenderer();
+
+    public PropertyDetailRenderer(LinkRenderer linkRenderer, GenerationListener listener) {
+        this.linkRenderer = linkRenderer;
+        this.listener = listener;
+    }
+
+    public void renderTo(PropertyDoc propertyDoc, Element parent) {
+        Document document = parent.getOwnerDocument();
+        Element section = document.createElement("section");
+        parent.appendChild(section);
+        section.setAttribute("id", propertyDoc.getId());
+        section.setAttribute("role", "detail");
+
+        Element title = document.createElement("title");
+        section.appendChild(title);
+        title.appendChild(linkRenderer.link(propertyDoc.getMetaData().getType(), listener));
+        title.appendChild(document.createTextNode(" "));
+        Element literal = document.createElement("literal");
+        title.appendChild(literal);
+        literal.appendChild(document.createTextNode(propertyDoc.getName()));
+        if (!propertyDoc.getMetaData().isWriteable()) {
+            title.appendChild(document.createTextNode(" (read-only)"));
+        }
+
+        warningsRenderer.renderTo(propertyDoc, "property", section);
+
+        for (Element element : propertyDoc.getComment()) {
+            section.appendChild(document.importNode(element, true));
+        }
+
+        if (!propertyDoc.getAdditionalValues().isEmpty()) {
+            Element segmentedlist = document.createElement("segmentedlist");
+            section.appendChild(segmentedlist);
+            for (ExtraAttributeDoc attributeDoc : propertyDoc.getAdditionalValues()) {
+                Element segtitle = document.createElement("segtitle");
+                segmentedlist.appendChild(segtitle);
+                for (Node node : attributeDoc.getTitle()) {
+                    segtitle.appendChild(document.importNode(node, true));
+                }
+            }
+            Element seglistitem = document.createElement("seglistitem");
+            segmentedlist.appendChild(seglistitem);
+            for (ExtraAttributeDoc attributeDoc : propertyDoc.getAdditionalValues()) {
+                Element seg = document.createElement("seg");
+                seglistitem.appendChild(seg);
+                for (Node node : attributeDoc.getValue()) {
+                    seg.appendChild(document.importNode(node, true));
+                }
+            }
+        }
+    }
+
+}
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
deleted file mode 100644
index 45eaef1..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
+++ /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.build.docs.dsl.docbook
-
-import org.gradle.build.docs.dsl.model.PropertyMetaData
-import org.w3c.dom.Element
-import org.gradle.build.docs.dsl.model.ClassMetaData
-
-class PropertyDoc implements DslElementDoc {
-    private final String id
-    private final String name
-    private final List<Element> comment
-    private final List<ExtraAttributeDoc> additionalValues
-    private final PropertyMetaData metaData
-
-    PropertyDoc(PropertyMetaData propertyMetaData, List<Element> comment, List<ExtraAttributeDoc> additionalValues) {
-        this(propertyMetaData.ownerClass, propertyMetaData, comment, additionalValues)
-    }
-
-    PropertyDoc(ClassMetaData referringClass, PropertyMetaData propertyMetaData, List<Element> comment, List<ExtraAttributeDoc> additionalValues) {
-        name = propertyMetaData.name
-        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
-    }
-
-    PropertyDoc forClass(ClassMetaData classMetaData, List<ExtraAttributeDoc> additionalValues) {
-        return new PropertyDoc(classMetaData, metaData, comment, additionalValues)
-    }
-
-    String getId() {
-        return id
-    }
-
-    String getName() {
-        return name
-    }
-
-    PropertyMetaData getMetaData() {
-        return metaData
-    }
-
-    boolean isDeprecated() {
-        return metaData.deprecated
-    }
-
-    boolean isExperimental() {
-        return metaData.experimental
-    }
-
-    Element getDescription() {
-        return comment.find { it.nodeName == 'para' }
-    }
-
-    List<Element> getComment() {
-        return comment
-    }
-
-    List<ExtraAttributeDoc> getAdditionalValues() {
-        return additionalValues
-    }
-}
-
-
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java
new file mode 100644
index 0000000..acb56c1
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class PropertyTableRenderer {
+    public void renderTo(Iterable<PropertyDoc> properties, Element parent) {
+        Document document = parent.getOwnerDocument();
+
+        // <thead>
+        //   <tr>
+        //     <td>Property</td>
+        //     <td>Description</td>
+        //   </tr>
+        // </thead>
+        Element thead = document.createElement("thead");
+        parent.appendChild(thead);
+        Element tr = document.createElement("tr");
+        thead.appendChild(tr);
+        Element td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Property"));
+        td = document.createElement("td");
+        tr.appendChild(td);
+        td.appendChild(document.createTextNode("Description"));
+
+        for (PropertyDoc propDoc : properties) {
+            // <tr>
+            //   <td><link linkend="$id"><literal>$name</literal></link</td>
+            //   <td>$description</td>
+            // </tr>
+            tr = document.createElement("tr");
+            parent.appendChild(tr);
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            Element link = document.createElement("link");
+            td.appendChild(link);
+            link.setAttribute("linkend", propDoc.getId());
+            Element literal = document.createElement("literal");
+            link.appendChild(literal);
+            literal.appendChild(document.createTextNode(propDoc.getName()));
+
+            td = document.createElement("td");
+            tr.appendChild(td);
+            if (propDoc.isDeprecated()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Deprecated"));
+            }
+            if (propDoc.isIncubating()) {
+                Element caution = document.createElement("caution");
+                td.appendChild(caution);
+                caution.appendChild(document.createTextNode("Incubating"));
+            }
+            td.appendChild(document.importNode(propDoc.getDescription(), true));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ReferencedTypeBuilder.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ReferencedTypeBuilder.java
new file mode 100644
index 0000000..b31cc5f
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ReferencedTypeBuilder.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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc;
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc;
+
+public class ReferencedTypeBuilder {
+    private final DslDocModel model;
+
+    public ReferencedTypeBuilder(DslDocModel model) {
+        this.model = model;
+    }
+
+    /**
+     * Builds the docs for types referenced by properties and methods of the given class.
+     */
+    public void build(ClassDoc classDoc) {
+        for (PropertyDoc propertyDoc : classDoc.getClassProperties()) {
+            String referencedType = propertyDoc.getMetaData().getType().getName();
+            if (!referencedType.equals(classDoc.getName())) {
+                model.findClassDoc(referencedType);
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy
new file mode 100644
index 0000000..4e4d553
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy
@@ -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.build.docs.dsl.docbook.model
+
+import org.w3c.dom.Element
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+
+class BlockDoc implements DslElementDoc {
+    private final MethodDoc blockMethod
+    private final PropertyDoc blockProperty
+    private final TypeMetaData type
+    private boolean multiValued
+
+    BlockDoc(MethodDoc blockMethod, PropertyDoc blockProperty, TypeMetaData type, boolean multiValued) {
+        this.blockMethod = blockMethod
+        this.type = type
+        this.blockProperty = blockProperty
+        this.multiValued = multiValued
+    }
+
+    BlockDoc forClass(ClassDoc referringClass) {
+        return new BlockDoc(blockMethod.forClass(referringClass), blockProperty.forClass(referringClass), type, multiValued)
+    }
+
+    String getId() {
+        return blockMethod.id
+    }
+
+    String getName() {
+        return blockMethod.name
+    }
+
+    boolean isMultiValued() {
+        return multiValued
+    }
+
+    TypeMetaData getType() {
+        return type
+    }
+
+    Element getDescription() {
+        return blockMethod.description;
+    }
+
+    List<Element> getComment() {
+        return blockMethod.comment
+    }
+
+    boolean isDeprecated() {
+        return blockProperty.deprecated || blockMethod.deprecated
+    }
+
+    boolean isIncubating() {
+        return blockProperty.incubating || blockMethod.incubating
+    }
+
+    PropertyDoc getBlockProperty() {
+        return blockProperty
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy
new file mode 100644
index 0000000..c60521b
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.model
+
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+class ClassDoc implements DslElementDoc {
+    private final String className
+    private final String id
+    private final String simpleName
+    final ClassMetaData classMetaData
+    private final Element classSection
+    final ClassExtensionMetaData extensionMetaData
+    private final List<PropertyDoc> classProperties = []
+    private final List<MethodDoc> classMethods = []
+    private final List<BlockDoc> classBlocks = []
+    private final List<ClassExtensionDoc> classExtensions = []
+    private final Element propertiesTable
+    private final Element methodsTable
+    private final Element propertiesSection
+    private final Element methodsSection
+    ClassDoc superClass
+    List<Element> comment = []
+
+    ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ClassExtensionMetaData extensionMetaData) {
+        this.className = className
+        id = className
+        simpleName = className.tokenize('.').last()
+        this.classMetaData = classMetaData
+        this.extensionMetaData = extensionMetaData
+
+        classSection = targetDocument.createElement('chapter')
+
+        classContent.childNodes.each { Node n ->
+            classSection << n
+        }
+
+        propertiesTable = getTable('Properties')
+        propertiesSection = propertiesTable.parentNode
+        methodsTable = getTable('Methods')
+        methodsSection = methodsTable.parentNode
+    }
+
+    String getId() { return id }
+
+    String getName() { return className }
+
+    String getSimpleName() { return simpleName }
+
+    boolean isDeprecated() {
+        return classMetaData.deprecated
+    }
+
+    boolean isIncubating() {
+        return classMetaData.incubating
+    }
+
+    Collection<PropertyDoc> getClassProperties() { return classProperties }
+
+    void addClassProperty(PropertyDoc propertyDoc) {
+        classProperties.add(propertyDoc.forClass(this))
+    }
+
+    Collection<MethodDoc> getClassMethods() { return classMethods }
+
+    void addClassMethod(MethodDoc methodDoc) {
+        classMethods.add(methodDoc.forClass(this))
+    }
+
+    Collection<BlockDoc> getClassBlocks() { return classBlocks }
+
+    void addClassBlock(BlockDoc blockDoc) {
+        classBlocks.add(blockDoc.forClass(this))
+    }
+
+    Collection<ClassExtensionDoc> getClassExtensions() { return classExtensions }
+
+    void addClassExtension(ClassExtensionDoc extensionDoc) {
+        classExtensions.add(extensionDoc)
+    }
+
+    Element getClassSection() { return classSection }
+
+    Element getPropertiesTable() { return propertiesTable }
+
+    def getPropertiesSection() { return propertiesSection }
+
+    def getPropertyDetailsSection() { return getSection('Property details') }
+
+    Element getMethodsTable() { return methodsTable }
+
+    def getMethodsSection() { return methodsSection }
+
+    def getMethodDetailsSection() { return getSection('Method details') }
+
+    def getBlocksTable() { return getTable('Script blocks') }
+
+    def getBlockDetailsSection() { return getSection('Script block details') }
+
+    ClassDoc mergeContent() {
+        classProperties.sort { it.name }
+        classMethods.sort { it.metaData.overrideSignature }
+        classBlocks.sort { it.name }
+        classExtensions.sort { it.pluginId }
+        return this
+    }
+
+    String getStyle() {
+        return classMetaData.groovy ? 'groovydoc' : 'javadoc'
+    }
+
+    private Element getTable(String title) {
+        def table = getSection(title).table[0]
+        if (!table) {
+            throw new RuntimeException("Section '$title' does not contain a <table> element.")
+        }
+        if (!table.thead[0]) {
+            throw new RuntimeException("Table '$title' does not contain a <thead> element.")
+        }
+        if (!table.thead[0].tr[0]) {
+            throw new RuntimeException("Table '$title' does not contain a <thead>/<tr> element.")
+        }
+        return table
+    }
+
+    private Element getSection(String title) {
+        def sections = classSection.section.findAll {
+            it.title[0] && it.title[0].text().trim() == title
+        }
+        if (sections.size() < 1) {
+            throw new RuntimeException("Docbook content for $className does not contain a '$title' section.")
+        }
+        return sections[0]
+    }
+
+    Element getDescription() {
+        if (comment.isEmpty() || comment[0].tagName != 'para') {
+            throw new RuntimeException("Class $className does not have a description paragraph.")
+        }
+        return comment[0]
+    }
+
+    PropertyDoc findProperty(String name) {
+        return classProperties.find { it.name == name }
+    }
+
+    BlockDoc getBlock(String name) {
+        def block = classBlocks.find { it.name == name }
+        if (block) {
+            return block
+        }
+        for (extensionDoc in classExtensions) {
+            block = extensionDoc.extensionBlocks.find { it.name == name }
+            if (block) {
+                return block
+            }
+        }
+        throw new RuntimeException("Class $className does not have a script block '$name'.")
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassExtensionDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassExtensionDoc.groovy
new file mode 100644
index 0000000..8717731
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassExtensionDoc.groovy
@@ -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.build.docs.dsl.docbook.model
+
+/**
+ * Represents the documentation model for extensions contributed by a given plugin.
+ */
+class ClassExtensionDoc {
+    private final Set<ClassDoc> mixinClasses = []
+    private final Map<String, ClassDoc> extensionClasses = [:]
+    private final String pluginId
+    final ClassDoc targetClass
+    final List<PropertyDoc> extraProperties = []
+    final List<BlockDoc> extraBlocks = []
+
+    ClassExtensionDoc(String pluginId, ClassDoc targetClass) {
+        this.pluginId = pluginId
+        this.targetClass = targetClass
+    }
+
+    String getPluginId() {
+        return pluginId
+    }
+
+    Set<ClassDoc> getMixinClasses() {
+        mixinClasses
+    }
+
+    Map<String, ClassDoc> getExtensionClasses() {
+        return extensionClasses
+    }
+
+    List<PropertyDoc> getExtensionProperties() {
+        List<PropertyDoc> properties = []
+        mixinClasses.each { mixin ->
+            mixin.classProperties.each { prop ->
+                properties << prop.forClass(targetClass)
+            }
+        }
+        extraProperties.each { prop ->
+            properties << prop.forClass(targetClass)
+        }
+        return properties.sort { it.name }
+    }
+
+    List<MethodDoc> getExtensionMethods() {
+        List<MethodDoc> methods = []
+        mixinClasses.each { mixin ->
+            mixin.classMethods.each { method ->
+                methods << method.forClass(targetClass)
+            }
+        }
+        return methods.sort { it.metaData.overrideSignature }
+    }
+
+    List<BlockDoc> getExtensionBlocks() {
+        List<BlockDoc> blocks = []
+        mixinClasses.each { mixin ->
+            mixin.classBlocks.each { block ->
+                blocks << block.forClass(targetClass)
+            }
+        }
+        extraBlocks.each { block->
+            blocks << block.forClass(targetClass)
+        }
+        return blocks.sort { it.name }
+    }
+}
+
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassExtensionMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassExtensionMetaData.groovy
new file mode 100644
index 0000000..bb30e8a
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/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.docbook.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/docbook/model/DslElementDoc.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.java
new file mode 100644
index 0000000..98b0742
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.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.build.docs.dsl.docbook.model;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public interface DslElementDoc {
+    String getId();
+
+    Element getDescription();
+
+    List<Element> getComment();
+
+    boolean isDeprecated();
+
+    boolean isIncubating();
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ExtensionMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ExtensionMetaData.groovy
new file mode 100644
index 0000000..e4dfaa1
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/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.docbook.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/docbook/model/ExtraAttributeDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ExtraAttributeDoc.groovy
new file mode 100644
index 0000000..26cb415
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ExtraAttributeDoc.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.build.docs.dsl.docbook.model
+
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+class ExtraAttributeDoc {
+    final Element titleCell
+    final Element valueCell
+
+    ExtraAttributeDoc(Element titleCell, Element valueCell) {
+        this.titleCell = titleCell
+        this.valueCell = valueCell
+    }
+
+    @Override
+    String toString() {
+        return "attribute[key: $key, value: $valueCell.textContent]"
+    }
+
+    String getKey() {
+        return titleCell.textContent
+    }
+
+    List<Node> getTitle() {
+        return titleCell.childNodes.collect { it }
+    }
+
+    List<Node> getValue() {
+        return valueCell.childNodes.collect { it }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy
new file mode 100644
index 0000000..6f685c4
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.build.docs.dsl.docbook.model
+
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
+import org.w3c.dom.Element
+
+class MethodDoc implements DslElementDoc {
+    private final String id
+    private final MethodMetaData metaData
+    private final List<Element> comment
+    private final ClassMetaData referringClass
+
+    MethodDoc(MethodMetaData metaData, List<Element> comment) {
+        this(metaData.ownerClass, metaData, comment)
+    }
+
+    MethodDoc(ClassMetaData referringClass, MethodMetaData metaData, List<Element> comment) {
+        this.metaData = metaData
+        this.referringClass = referringClass
+        id = "$referringClass.className:$metaData.overrideSignature"
+        this.comment = comment
+    }
+
+    MethodDoc forClass(ClassDoc referringClass) {
+        def refererMetaData = referringClass.classMetaData
+        if (refererMetaData == this.referringClass) {
+            return this
+        }
+        return new MethodDoc(refererMetaData, metaData, comment)
+    }
+
+    String getId() {
+        return id
+    }
+
+    String getName() {
+        return metaData.name
+    }
+
+    MethodMetaData getMetaData() {
+        return metaData
+    }
+
+    boolean isDeprecated() {
+        return metaData.deprecated && !referringClass.deprecated
+    }
+
+    boolean isIncubating() {
+        return metaData.incubating || metaData.ownerClass.incubating
+    }
+
+    Element getDescription() {
+        return comment.find { it.nodeName == 'para' }
+    }
+
+    List<Element> getComment() {
+        return comment
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MixinMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MixinMetaData.groovy
new file mode 100644
index 0000000..a0b1961
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/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.docbook.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/docs/dsl/docbook/model/PropertyDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy
new file mode 100644
index 0000000..47e6a0a
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy
@@ -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.build.docs.dsl.docbook.model
+
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData
+import org.w3c.dom.Element
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+
+class PropertyDoc implements DslElementDoc {
+    private final String id
+    private final String name
+    private final List<Element> comment
+    private final List<ExtraAttributeDoc> additionalValues
+    private final PropertyMetaData metaData
+    private final ClassMetaData referringClass
+
+    PropertyDoc(PropertyMetaData propertyMetaData, List<Element> comment, List<ExtraAttributeDoc> additionalValues) {
+        this(propertyMetaData.ownerClass, propertyMetaData, comment, additionalValues)
+    }
+
+    PropertyDoc(ClassMetaData referringClass, PropertyMetaData propertyMetaData, List<Element> comment, List<ExtraAttributeDoc> additionalValues) {
+        name = propertyMetaData.name
+        this.referringClass = referringClass
+        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
+    }
+
+    PropertyDoc forClass(ClassDoc referringClass) {
+        return forClass(referringClass, [])
+    }
+
+    PropertyDoc forClass(ClassDoc referringClass, Collection<ExtraAttributeDoc> additionalValues) {
+        def refererMetaData = referringClass.classMetaData
+        if (refererMetaData == this.referringClass && additionalValues.isEmpty()) {
+            return this
+        }
+        return new PropertyDoc(refererMetaData, metaData, comment, additionalValues as List)
+    }
+
+    String getId() {
+        return id
+    }
+
+    String getName() {
+        return name
+    }
+
+    PropertyMetaData getMetaData() {
+        return metaData
+    }
+
+    boolean isDeprecated() {
+        return metaData.deprecated && !referringClass.deprecated
+    }
+
+    boolean isIncubating() {
+        return metaData.incubating || metaData.ownerClass.incubating
+    }
+
+    Element getDescription() {
+        return comment.find { it.nodeName == 'para' }
+    }
+
+    List<Element> getComment() {
+        return comment
+    }
+
+    List<ExtraAttributeDoc> getAdditionalValues() {
+        return additionalValues
+    }
+}
+
+
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java
new file mode 100644
index 0000000..a831f42
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.links;
+
+import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+import org.gradle.build.docs.dsl.source.model.MethodMetaData;
+import org.gradle.build.docs.model.Attachable;
+import org.gradle.build.docs.model.ClassMetaDataRepository;
+import org.gradle.util.CollectionUtils;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+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, style);
+        }
+    }
+
+    public LinkMetaData getClassLink() {
+        return new LinkMetaData(style, simpleName, null);
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public LinkMetaData getMethod(String method) {
+        MethodLinkMetaData methodMetaData = findMethod(method);
+        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) {
+        MethodLinkMetaData metaData = methods.get(method);
+        if (metaData != null) {
+            return metaData;
+        }
+
+        List<MethodLinkMetaData> candidates = new ArrayList<MethodLinkMetaData>();
+        for (MethodLinkMetaData methodLinkMetaData : methods.values()) {
+            if (methodLinkMetaData.name.equals(method)) {
+                candidates.add(methodLinkMetaData);
+            }
+        }
+        if (candidates.isEmpty()) {
+            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) {
+            String message = String.format("Found multiple methods called '%s' in class '%s'. Candidates: %s",
+                    method, className, CollectionUtils.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);
+    }
+
+    public LinkMetaData.Style getStyle() {
+        return style;
+    }
+
+    public void setStyle(LinkMetaData.Style style) {
+        this.style = 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 {
+        final String name;
+        final String signature;
+        final LinkMetaData.Style style;
+
+        private MethodLinkMetaData(String name, String signature, LinkMetaData.Style style) {
+            this.name = name;
+            this.signature = signature;
+            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/links/LinkMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/LinkMetaData.java
new file mode 100644
index 0000000..388f594
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/LinkMetaData.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.build.docs.dsl.links;
+
+import java.io.Serializable;
+
+public class LinkMetaData implements Serializable {
+    public enum Style { Javadoc, Groovydoc, Dsldoc }
+
+    private final Style style;
+    private final String displayName;
+    private final String urlFragment;
+
+    public LinkMetaData(Style style, String displayName, String urlFragment) {
+        this.style = style;
+        this.displayName = displayName;
+        this.urlFragment = urlFragment;
+    }
+
+    public Style getStyle() {
+        return style;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getUrlFragment() {
+        return urlFragment;
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java
deleted file mode 100644
index c23cf52..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-import org.gradle.api.Transformer;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-public abstract class AbstractLanguageElement implements LanguageElement, Serializable {
-    private String rawCommentText;
-    private final List<String> annotationNames = new ArrayList<String>();
-
-    protected AbstractLanguageElement() {
-    }
-
-    protected AbstractLanguageElement(String rawCommentText) {
-        this.rawCommentText = rawCommentText;
-    }
-
-    public String getRawCommentText() {
-        return rawCommentText;
-    }
-
-    public void setRawCommentText(String rawCommentText) {
-        this.rawCommentText = rawCommentText;
-    }
-
-    public List<String> getAnnotationTypeNames() {
-        return annotationNames;
-    }
-
-    public void addAnnotationTypeName(String annotationType) {
-        annotationNames.add(annotationType);
-    }
-
-    public boolean isDeprecated() {
-        return annotationNames.contains(Deprecated.class.getName());
-    }
-
-    public boolean isExperimental() {
-        return annotationNames.contains("org.gradle.api.Experimental");
-    }
-
-    public void resolveTypes(Transformer<String, String> transformer) {
-        for (int i = 0; i < annotationNames.size(); i++) {
-            annotationNames.set(i, transformer.transform(annotationNames.get(i)));
-        }
-    }
-}
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
deleted file mode 100644
index d08f30f..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassExtensionMetaData.groovy
+++ /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.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/ClassMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.java
deleted file mode 100644
index ba3324d..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.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.build.docs.dsl.model;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.Action;
-import org.gradle.api.Transformer;
-import org.gradle.build.docs.model.Attachable;
-import org.gradle.build.docs.model.ClassMetaDataRepository;
-import org.gradle.util.GUtil;
-
-import java.io.Serializable;
-import java.util.*;
-
-public class ClassMetaData extends AbstractLanguageElement implements Serializable, Attachable<ClassMetaData>, TypeContainer {
-    private final String className;
-    private String superClassName;
-    private final String packageName;
-    private final boolean isInterface;
-    private final boolean isGroovy;
-    private final List<String> imports = new ArrayList<String>();
-    private final List<String> interfaceNames = new ArrayList<String>();
-    private final Map<String, PropertyMetaData> declaredProperties = new HashMap<String, PropertyMetaData>();
-    private final Set<MethodMetaData> declaredMethods = new HashSet<MethodMetaData>();
-    private final List<String> innerClassNames = new ArrayList<String>();
-    private String outerClassName;
-    private transient ClassMetaDataRepository<ClassMetaData> metaDataRepository;
-    public final HashMap<String, String> constants = new HashMap<String, String>();
-
-    public ClassMetaData(String className, String packageName, boolean isInterface, boolean isGroovy, String rawClassComment) {
-        super(rawClassComment);
-        this.className = className;
-        this.packageName = packageName;
-        this.isInterface = isInterface;
-        this.isGroovy = isGroovy;
-    }
-
-    public ClassMetaData(String className) {
-        this(className, StringUtils.substringBeforeLast(className, "."), false, false, "");
-    }
-
-    @Override
-    public String toString() {
-        return className;
-    }
-
-    public String getClassName() {
-        return className;
-    }
-
-    public String getSimpleName() {
-        return StringUtils.substringAfterLast(className, ".");
-    }
-
-    public String getPackageName() {
-        return packageName;
-    }
-
-    public boolean isInterface() {
-        return isInterface;
-    }
-
-    public boolean isGroovy() {
-        return isGroovy;
-    }
-
-    public String getSuperClassName() {
-        return superClassName;
-    }
-
-    public void setSuperClassName(String superClassName) {
-        this.superClassName = superClassName;
-    }
-
-    public ClassMetaData getSuperClass() {
-        return superClassName == null ? null : metaDataRepository.find(superClassName);
-    }
-
-    public List<String> getInterfaceNames() {
-        return interfaceNames;
-    }
-
-    public void addInterfaceName(String name) {
-        interfaceNames.add(name);
-    }
-
-    public List<ClassMetaData> getInterfaces() {
-        List<ClassMetaData> interfaces = new ArrayList<ClassMetaData>();
-        for (String interfaceName : interfaceNames) {
-            ClassMetaData interfaceMetaData = metaDataRepository.find(interfaceName);
-            if (interfaceMetaData != null) {
-                interfaces.add(interfaceMetaData);
-            }
-        }
-        return interfaces;
-    }
-
-    public List<String> getInnerClassNames() {
-        return innerClassNames;
-    }
-
-    public void addInnerClassName(String innerClassName) {
-        innerClassNames.add(innerClassName);
-    }
-
-    public String getOuterClassName() {
-        return outerClassName;
-    }
-
-    public void setOuterClassName(String outerClassName) {
-        this.outerClassName = outerClassName;
-    }
-
-    public List<String> getImports() {
-        return imports;
-    }
-
-    public void addImport(String importName) {
-        imports.add(importName);
-    }
-
-    public PropertyMetaData addReadableProperty(String name, TypeMetaData type, String rawCommentText, MethodMetaData getterMethod) {
-        PropertyMetaData property = getProperty(name);
-        property.setType(type);
-        property.setRawCommentText(rawCommentText);
-        property.setGetter(getterMethod);
-        return property;
-    }
-
-    public PropertyMetaData addWriteableProperty(String name, TypeMetaData type, String rawCommentText, MethodMetaData setterMethod) {
-        PropertyMetaData property = getProperty(name);
-        if (property.getType() == null) {
-            property.setType(type);
-        }
-        if (!GUtil.isTrue(property.getRawCommentText())) {
-            property.setRawCommentText(rawCommentText);
-        }
-        property.setSetter(setterMethod);
-        return property;
-    }
-
-    public PropertyMetaData findDeclaredProperty(String name) {
-        return declaredProperties.get(name);
-    }
-
-    public Set<String> getDeclaredPropertyNames() {
-        return declaredProperties.keySet();
-    }
-
-    public Set<PropertyMetaData> getDeclaredProperties() {
-        return new HashSet<PropertyMetaData>(declaredProperties.values());
-    }
-
-    public Set<MethodMetaData> getDeclaredMethods() {
-        return declaredMethods;
-    }
-
-    public MethodMetaData findDeclaredMethod(String signature) {
-        for (MethodMetaData method : declaredMethods) {
-            if (method.getOverrideSignature().equals(signature)) {
-                return method;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Finds a property by name. Includes inherited properties.
-     *
-     * @param name The property name.
-     * @return The property, or null if no such property exists.
-     */
-    public PropertyMetaData findProperty(String name) {
-        PropertyMetaData propertyMetaData = declaredProperties.get(name);
-        if (propertyMetaData != null) {
-            return propertyMetaData;
-        }
-        ClassMetaData superClass = getSuperClass();
-        if (superClass != null) {
-            return superClass.findProperty(name);
-        }
-        return null;
-    }
-
-    /**
-     * Returns the set of property names for this class, including inherited properties.
-     *
-     * @return The set of property names.
-     */
-    public Set<String> getPropertyNames() {
-        Set<String> propertyNames = new TreeSet<String>();
-        propertyNames.addAll(declaredProperties.keySet());
-        ClassMetaData superClass = getSuperClass();
-        if (superClass != null) {
-            propertyNames.addAll(superClass.getPropertyNames());
-        }
-        return propertyNames;
-    }
-
-    private PropertyMetaData getProperty(String name) {
-        PropertyMetaData property = declaredProperties.get(name);
-        if (property == null) {
-            property = new PropertyMetaData(name, this);
-            declaredProperties.put(name, property);
-        }
-        return property;
-    }
-
-    public Map<String, String> getConstants() {
-        return constants;
-    }
-    
-    public void attach(ClassMetaDataRepository<ClassMetaData> metaDataRepository) {
-        this.metaDataRepository = metaDataRepository;
-    }
-
-    public MethodMetaData addMethod(String name, TypeMetaData returnType, String rawCommentText) {
-        MethodMetaData method = new MethodMetaData(name, this);
-        declaredMethods.add(method);
-        method.setReturnType(returnType);
-        method.setRawCommentText(rawCommentText);
-        return method;
-    }
-
-    public void resolveTypes(Transformer<String, String> transformer) {
-        super.resolveTypes(transformer);
-        if (superClassName != null) {
-            superClassName = transformer.transform(superClassName);
-        }
-        for (int i = 0; i < interfaceNames.size(); i++) {
-            interfaceNames.set(i, transformer.transform(interfaceNames.get(i)));
-        }
-        for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
-            propertyMetaData.resolveTypes(transformer);
-        }
-        for (MethodMetaData methodMetaData : declaredMethods) {
-            methodMetaData.resolveTypes(transformer);
-        }
-    }
-
-    public void visitTypes(Action<TypeMetaData> action) {
-        for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
-            propertyMetaData.visitTypes(action);
-        }
-        for (MethodMetaData methodMetaData : declaredMethods) {
-            methodMetaData.visitTypes(action);
-        }
-    }
-}
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
deleted file mode 100644
index 3e34d63..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ExtensionMetaData.groovy
+++ /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.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/LanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.java
deleted file mode 100644
index d931ff3..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.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.build.docs.dsl.model;
-
-import java.util.List;
-
-public interface LanguageElement {
-    String getRawCommentText();
-
-    List<String> getAnnotationTypeNames();
-
-    boolean isDeprecated();
-
-    boolean isExperimental();
-}
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
deleted file mode 100644
index 4bcbcab..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
+++ /dev/null
@@ -1,142 +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.model;
-
-import org.gradle.api.Action;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-public class MethodMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
-    private final String name;
-    private final ClassMetaData ownerClass;
-    private final List<ParameterMetaData> parameters = new ArrayList<ParameterMetaData>();
-    private TypeMetaData returnType;
-
-    public MethodMetaData(String name, ClassMetaData ownerClass) {
-        this.name = name;
-        this.ownerClass = ownerClass;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s.%s()", ownerClass, name);
-    }
-
-    public ClassMetaData getOwnerClass() {
-        return ownerClass;
-    }
-
-    public TypeMetaData getReturnType() {
-        return returnType;
-    }
-
-    public void setReturnType(TypeMetaData returnType) {
-        this.returnType = returnType;
-    }
-
-    public MethodMetaData getOverriddenMethod() {
-        LinkedList<ClassMetaData> queue = new LinkedList<ClassMetaData>();
-        queue.add(ownerClass.getSuperClass());
-        queue.addAll(ownerClass.getInterfaces());
-        
-        String overrideSignature = getOverrideSignature();
-
-        while (!queue.isEmpty()) {
-            ClassMetaData cl = queue.removeFirst();
-            if (cl == null) {
-                continue;
-            }
-            MethodMetaData overriddenMethod = cl.findDeclaredMethod(overrideSignature);
-            if (overriddenMethod != null) {
-                return overriddenMethod;
-            }
-            queue.add(cl.getSuperClass());
-            queue.addAll(cl.getInterfaces());
-        }
-
-        return null;
-    }
-
-    public List<ParameterMetaData> getParameters() {
-        return parameters;
-    }
-
-    public ParameterMetaData addParameter(String name, TypeMetaData type) {
-        ParameterMetaData param = new ParameterMetaData(name, this);
-        param.setType(type);
-        parameters.add(param);
-        return param;
-    }
-
-    @Override
-    public boolean isDeprecated() {
-        return super.isDeprecated() || ownerClass.isDeprecated();
-    }
-
-    @Override
-    public boolean isExperimental() {
-        return super.isExperimental() || ownerClass.isExperimental();
-    }
-
-    public String getSignature() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(returnType.getSignature());
-        builder.append(' ');
-        builder.append(name);
-        builder.append('(');
-        for (int i = 0; i < parameters.size(); i++) {
-            ParameterMetaData param =  parameters.get(i);
-            if (i > 0) {
-                builder.append(", ");
-            }
-            builder.append(param.getSignature());
-        }
-        builder.append(')');
-        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);
-        builder.append('(');
-        for (int i = 0; i < parameters.size(); i++) {
-            ParameterMetaData param =  parameters.get(i);
-            if (i > 0) {
-                builder.append(", ");
-            }
-            builder.append(param.getType().getRawType().getSignature());
-        }
-        builder.append(')');
-        return builder.toString();
-    }
-
-    public void visitTypes(Action<TypeMetaData> action) {
-        action.execute(returnType);
-        for (ParameterMetaData parameter : parameters) {
-            parameter.visitTypes(action);
-        }
-    }
-}
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
deleted file mode 100644
index 843beed..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MixinMetaData.groovy
+++ /dev/null
@@ -1,26 +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.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/docs/dsl/model/ParameterMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ParameterMetaData.java
deleted file mode 100644
index ff54140..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ParameterMetaData.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.build.docs.dsl.model;
-
-import org.gradle.api.Action;
-
-import java.io.Serializable;
-
-public class ParameterMetaData implements Serializable, TypeContainer {
-    private final MethodMetaData ownerMethod;
-    private final String name;
-    private TypeMetaData type;
-
-    public ParameterMetaData(String name, MethodMetaData ownerMethod) {
-        this.name = name;
-        this.ownerMethod = ownerMethod;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public TypeMetaData getType() {
-        return type;
-    }
-
-    public void setType(TypeMetaData type) {
-        this.type = type;
-    }
-
-    public String getSignature() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(type.getSignature());
-        builder.append(" ");
-        builder.append(name);
-        return builder.toString();
-    }
-
-    public void visitTypes(Action<TypeMetaData> action) {
-        action.execute(type);
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java
deleted file mode 100644
index 91d2128..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java
+++ /dev/null
@@ -1,111 +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.model;
-
-import org.gradle.api.Action;
-
-import java.io.Serializable;
-
-public class PropertyMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
-    private TypeMetaData type;
-    private final String name;
-    private final ClassMetaData ownerClass;
-    private MethodMetaData setter;
-    private MethodMetaData getter;
-
-    public PropertyMetaData(String name, ClassMetaData ownerClass) {
-        this.name = name;
-        this.ownerClass = ownerClass;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s.%s", ownerClass, name);
-    }
-
-    public TypeMetaData getType() {
-        return type;
-    }
-
-    public void setType(TypeMetaData type) {
-        this.type = type;
-    }
-
-    public boolean isWriteable() {
-        return setter != null;
-    }
-
-    public ClassMetaData getOwnerClass() {
-        return ownerClass;
-    }
-
-    @Override
-    public boolean isDeprecated() {
-        return super.isDeprecated() || ownerClass.isDeprecated();
-    }
-
-    @Override
-    public boolean isExperimental() {
-        return super.isExperimental() || ownerClass.isExperimental();
-    }
-
-    public String getSignature() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(type.getSignature());
-        builder.append(' ');
-        builder.append(name);
-        return builder.toString();
-    }
-
-    public MethodMetaData getGetter() {
-        return getter;
-    }
-
-    public void setGetter(MethodMetaData getter) {
-        this.getter = getter;
-    }
-
-    public MethodMetaData getSetter() {
-        return setter;
-    }
-
-    public void setSetter(MethodMetaData setter) {
-        this.setter = setter;
-    }
-
-    public PropertyMetaData getOverriddenProperty() {
-        MethodMetaData overriddenMethod = null;
-        if (getter != null) {
-            overriddenMethod = getter.getOverriddenMethod();
-        }
-        if (overriddenMethod == null && setter != null) {
-            overriddenMethod = setter.getOverriddenMethod();
-        }
-        if (overriddenMethod != null) {
-            return overriddenMethod.getOwnerClass().findDeclaredProperty(name);
-        }
-
-        return null;
-    }
-
-    public void visitTypes(Action<TypeMetaData> action) {
-        action.execute(type);
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeContainer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeContainer.java
deleted file mode 100644
index 2a86013..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeContainer.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.build.docs.dsl.model;
-
-import org.gradle.api.Action;
-
-public interface TypeContainer {
-    void visitTypes(Action<TypeMetaData> action);
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeMetaData.java
deleted file mode 100644
index fbfd7a9..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/TypeMetaData.java
+++ /dev/null
@@ -1,186 +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.model;
-
-import org.gradle.api.Action;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TypeMetaData implements Serializable, TypeContainer {
-    public static final TypeMetaData VOID = new TypeMetaData("void");
-    public static final TypeMetaData OBJECT = new TypeMetaData("java.lang.Object");
-
-    private String name;
-    private int arrayDimensions;
-    private boolean varargs;
-    private List<TypeMetaData> typeArgs;
-    private boolean wildcard;
-    private TypeMetaData upperBounds;
-    private TypeMetaData lowerBounds;
-
-    public TypeMetaData(String name) {
-        this.name = name;
-    }
-
-    public TypeMetaData() {
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public int getArrayDimensions() {
-        return arrayDimensions + (varargs ? 1 : 0);
-    }
-
-    public TypeMetaData addArrayDimension() {
-        arrayDimensions++;
-        return this;
-    }
-
-    public boolean isVarargs() {
-        return varargs;
-    }
-
-    public TypeMetaData setVarargs() {
-        this.varargs = true;
-        return this;
-    }
-
-    public TypeMetaData getRawType() {
-        if (wildcard || lowerBounds != null) {
-            return OBJECT;
-        }
-        if (upperBounds != null) {
-            return upperBounds.getRawType();
-        }
-        TypeMetaData rawType = new TypeMetaData(name);
-        rawType.arrayDimensions = arrayDimensions;
-        rawType.varargs = varargs;
-        return rawType;
-    }
-
-    public String getSignature() {
-        final StringBuilder builder = new StringBuilder();
-
-        visitSignature(new SignatureVisitor() {
-            public void visitText(String text) {
-                builder.append(text);
-            }
-
-            public void visitType(String name) {
-                builder.append(name);
-            }
-        });
-        return builder.toString();
-    }
-
-    public String getArraySuffix() {
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < arrayDimensions; i++) {
-            builder.append("[]");
-        }
-        if (varargs) {
-            builder.append("...");
-        }
-        return builder.toString();
-    }
-
-    public TypeMetaData addTypeArg(TypeMetaData typeArg) {
-        if (typeArgs == null) {
-            typeArgs = new ArrayList<TypeMetaData>();
-        }
-        typeArgs.add(typeArg);
-        return this;
-    }
-
-    public void visitTypes(Action<TypeMetaData> action) {
-        if (wildcard) {
-            return;
-        }
-        if (upperBounds != null) {
-            upperBounds.visitTypes(action);
-            return;
-        }
-        if (lowerBounds != null) {
-            lowerBounds.visitTypes(action);
-            return;
-        }
-        
-        action.execute(this);
-        if (typeArgs != null) {
-            for (TypeMetaData typeArg : typeArgs) {
-                typeArg.visitTypes(action);
-            }
-        }
-    }
-
-    public void visitSignature(SignatureVisitor visitor) {
-        if (wildcard) {
-            visitor.visitText("?");
-        } else if (upperBounds != null) {
-            visitor.visitText("? extends ");
-            upperBounds.visitSignature(visitor);
-        } else if (lowerBounds != null) {
-            visitor.visitText("? super ");
-            lowerBounds.visitSignature(visitor);
-        } else {
-            visitor.visitType(name);
-            if (typeArgs != null) {
-                visitor.visitText("<");
-                for (int i = 0; i < typeArgs.size(); i++) {
-                    if (i > 0) {
-                        visitor.visitText(", ");
-                    }
-                    TypeMetaData typeArg = typeArgs.get(i);
-                    typeArg.visitSignature(visitor);
-                }
-                visitor.visitText(">");
-            }
-            String suffix = getArraySuffix();
-            if (suffix.length() > 0) {
-                visitor.visitText(suffix);
-            }
-        }
-    }
-
-    public TypeMetaData setWildcard() {
-        wildcard = true;
-        return this;
-    }
-
-    public TypeMetaData setUpperBounds(TypeMetaData upperBounds) {
-        this.upperBounds = upperBounds;
-        return this;
-    }
-
-    public TypeMetaData setLowerBounds(TypeMetaData lowerBounds) {
-        this.lowerBounds = lowerBounds;
-        return this;
-    }
-
-    public interface SignatureVisitor {
-        void visitText(String text);
-
-        void visitType(String name);
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTask.groovy
new file mode 100644
index 0000000..9ca6c6c
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTask.groovy
@@ -0,0 +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.build.docs.dsl.source
+
+import groovyjarjarantlr.collections.AST
+import org.codehaus.groovy.antlr.AntlrASTProcessor
+import org.codehaus.groovy.antlr.SourceBuffer
+import org.codehaus.groovy.antlr.UnicodeEscapingReader
+import org.codehaus.groovy.antlr.java.Java2GroovyConverter
+import org.codehaus.groovy.antlr.java.JavaLexer
+import org.codehaus.groovy.antlr.java.JavaRecognizer
+import org.codehaus.groovy.antlr.parser.GroovyLexer
+import org.codehaus.groovy.antlr.parser.GroovyRecognizer
+import org.codehaus.groovy.antlr.treewalker.PreOrderTraversal
+import org.codehaus.groovy.antlr.treewalker.SourceCodeTraversal
+import org.codehaus.groovy.antlr.treewalker.Visitor
+import org.gradle.api.Action
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.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
+import org.gradle.api.Transformer
+
+/**
+ * Extracts meta-data from the Groovy and Java source files which make up the Gradle DSL. Persists the meta-data to a file
+ * for later use in generating the docbook source for the DSL, such as by {@link org.gradle.build.docs.dsl.docbook.AssembleDslDocTask}.
+ */
+class ExtractDslMetaDataTask extends SourceTask {
+    @OutputFile
+    def File destFile
+
+    @TaskAction
+    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 ->
+            parse(f, repository)
+            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 ->
+            fullyQualifyAllTypeNames(metaData, resolver)
+        }
+        repository.store(destFile)
+
+        println "Parsed $counter classes in ${clock.time}"
+    }
+
+    def parse(File sourceFile, ClassMetaDataRepository<ClassMetaData> repository) {
+        try {
+            sourceFile.withReader { reader ->
+                if (sourceFile.name.endsWith('.java')) {
+                    parseJava(sourceFile, reader, repository)
+                } else {
+                    parseGroovy(sourceFile, reader, repository)
+                }
+            }
+        } catch (Exception e) {
+            throw new DocGenerationException("Could not parse '$sourceFile'.", e)
+        }
+    }
+
+    def parseJava(File sourceFile, Reader input, ClassMetaDataRepository<ClassMetaData> repository) {
+        SourceBuffer sourceBuffer = new SourceBuffer();
+        UnicodeEscapingReader unicodeReader = new UnicodeEscapingReader(input, sourceBuffer);
+        JavaLexer lexer = new JavaLexer(unicodeReader);
+        unicodeReader.setLexer(lexer);
+        JavaRecognizer parser = JavaRecognizer.make(lexer);
+        parser.setSourceBuffer(sourceBuffer);
+        String[] tokenNames = parser.getTokenNames();
+
+        parser.compilationUnit();
+        AST ast = parser.getAST();
+
+        // modify the Java AST into a Groovy AST (just token types)
+        Visitor java2groovyConverter = new Java2GroovyConverter(tokenNames);
+        AntlrASTProcessor java2groovyTraverser = new PreOrderTraversal(java2groovyConverter);
+        java2groovyTraverser.process(ast);
+
+        def visitor = new SourceMetaDataVisitor(sourceBuffer, repository, false)
+        AntlrASTProcessor traverser = new SourceCodeTraversal(visitor);
+        traverser.process(ast);
+        visitor.complete()
+    }
+
+    def parseGroovy(File sourceFile, Reader input, ClassMetaDataRepository<ClassMetaData> repository) {
+        SourceBuffer sourceBuffer = new SourceBuffer();
+        UnicodeEscapingReader unicodeReader = new UnicodeEscapingReader(input, sourceBuffer);
+        GroovyLexer lexer = new GroovyLexer(unicodeReader);
+        unicodeReader.setLexer(lexer);
+        GroovyRecognizer parser = GroovyRecognizer.make(lexer);
+        parser.setSourceBuffer(sourceBuffer);
+
+        parser.compilationUnit();
+        AST ast = parser.getAST();
+
+        def visitor = new SourceMetaDataVisitor(sourceBuffer, repository, true)
+        AntlrASTProcessor traverser = new SourceCodeTraversal(visitor);
+        traverser.process(ast);
+        visitor.complete()
+    }
+
+    def fullyQualifyAllTypeNames(ClassMetaData classMetaData, TypeNameResolver resolver) {
+        try {
+            classMetaData.resolveTypes(new Transformer<String, String>(){
+                String transform(String i) {
+                    return resolver.resolve(i, classMetaData)
+                }
+            })
+            classMetaData.visitTypes(new Action<TypeMetaData>() {
+                void execute(TypeMetaData t) {
+                    resolver.resolve(t, classMetaData)
+                }
+            })
+        } catch (Exception e) {
+            throw new RuntimeException("Could not resolve types in class '$classMetaData.className'.", e)
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java
new file mode 100644
index 0000000..40915fc
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source;
+
+import groovyjarjarantlr.collections.AST;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.antlr.GroovySourceAST;
+import org.codehaus.groovy.antlr.LineColumn;
+import org.codehaus.groovy.antlr.SourceBuffer;
+import org.codehaus.groovy.antlr.treewalker.VisitorAdapter;
+import org.gradle.build.docs.dsl.source.model.*;
+import org.gradle.build.docs.model.ClassMetaDataRepository;
+
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.*;
+
+public class SourceMetaDataVisitor extends VisitorAdapter {
+    private static final Pattern PREV_JAVADOC_COMMENT_PATTERN = Pattern.compile("(?s)/\\*\\*(.*?)\\*/");
+    private static final Pattern GETTER_METHOD_NAME = Pattern.compile("(get|is)(.+)");
+    private static final Pattern SETTER_METHOD_NAME = Pattern.compile("set(.+)");
+    private final SourceBuffer sourceBuffer;
+    private final LinkedList<GroovySourceAST> parseStack = new LinkedList<GroovySourceAST>();
+    private final List<String> imports = new ArrayList<String>();
+    private final ClassMetaDataRepository<ClassMetaData> repository;
+    private final List<ClassMetaData> allClasses = new ArrayList<ClassMetaData>();
+    private final LinkedList<ClassMetaData> classStack = new LinkedList<ClassMetaData>();
+    private final Map<GroovySourceAST, ClassMetaData> typeTokens = new HashMap<GroovySourceAST, ClassMetaData>();
+    private final boolean groovy;
+    private String packageName;
+    private LineColumn lastLineCol;
+
+    SourceMetaDataVisitor(SourceBuffer sourceBuffer, ClassMetaDataRepository<ClassMetaData> repository,
+                          boolean isGroovy) {
+        this.sourceBuffer = sourceBuffer;
+        this.repository = repository;
+        groovy = isGroovy;
+        lastLineCol = new LineColumn(1, 1);
+    }
+
+    public void complete() {
+        for (String anImport : imports) {
+            for (ClassMetaData classMetaData : allClasses) {
+                classMetaData.addImport(anImport);
+            }
+        }
+    }
+
+    @Override
+    public void visitPackageDef(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            packageName = extractName(t);
+        }
+    }
+
+    @Override
+    public void visitImport(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            imports.add(extractName(t));
+        }
+    }
+
+    @Override
+    public void visitClassDef(GroovySourceAST t, int visit) {
+        visitTypeDef(t, visit, false);
+    }
+
+    @Override
+    public void visitInterfaceDef(GroovySourceAST t, int visit) {
+        visitTypeDef(t, visit, true);
+    }
+
+    @Override
+    public void visitEnumDef(GroovySourceAST t, int visit) {
+        visitTypeDef(t, visit, false);
+    }
+
+    @Override
+    public void visitAnnotationDef(GroovySourceAST t, int visit) {
+        visitTypeDef(t, visit, false);
+    }
+
+    private void visitTypeDef(GroovySourceAST t, int visit, boolean isInterface) {
+        if (visit == OPENING_VISIT) {
+            ClassMetaData outerClass = getCurrentClass();
+            String baseName = extractIdent(t);
+            String className = outerClass != null ? outerClass.getClassName() + '.' + baseName
+                    : packageName + '.' + baseName;
+            String comment = getJavaDocCommentsBeforeNode(t);
+            ClassMetaData currentClass = new ClassMetaData(className, packageName, isInterface, groovy, comment);
+            if (outerClass != null) {
+                outerClass.addInnerClassName(className);
+                currentClass.setOuterClassName(outerClass.getClassName());
+            }
+            findAnnotations(t, currentClass);
+            classStack.addFirst(currentClass);
+            allClasses.add(currentClass);
+            typeTokens.put(t, currentClass);
+            repository.put(className, currentClass);
+        }
+    }
+
+    private ClassMetaData getCurrentClass() {
+        return classStack.isEmpty() ? null : classStack.getFirst();
+    }
+
+    @Override
+    public void visitExtendsClause(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            ClassMetaData currentClass = getCurrentClass();
+            for (
+                    GroovySourceAST child = (GroovySourceAST) t.getFirstChild(); child != null;
+                    child = (GroovySourceAST) child.getNextSibling()) {
+                if (!currentClass.isInterface()) {
+                    currentClass.setSuperClassName(extractName(child));
+                } else {
+                    currentClass.addInterfaceName(extractName(child));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visitImplementsClause(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            ClassMetaData currentClass = getCurrentClass();
+            for (
+                    GroovySourceAST child = (GroovySourceAST) t.getFirstChild(); child != null;
+                    child = (GroovySourceAST) child.getNextSibling()) {
+                currentClass.addInterfaceName(extractName(child));
+            }
+        }
+    }
+
+    @Override
+    public void visitMethodDef(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            maybeAddMethod(t);
+            skipJavaDocComment(t);
+        }
+    }
+
+    private void maybeAddMethod(GroovySourceAST t) {
+        String name = extractName(t);
+        if (!groovy && name.equals(getCurrentClass().getSimpleName())) {
+            // A constructor. The java grammar treats a constructor as a method, the groovy grammar does not.
+            return;
+        }
+
+        ASTIterator children = new ASTIterator(t);
+        if (groovy) {
+            children.skip(TYPE_PARAMETERS);
+            children.skip(MODIFIERS);
+        } else {
+            children.skip(MODIFIERS);
+            children.skip(TYPE_PARAMETERS);
+        }
+
+        String rawCommentText = getJavaDocCommentsBeforeNode(t);
+        TypeMetaData returnType = extractTypeName(children.current);
+        MethodMetaData method = getCurrentClass().addMethod(name, returnType, rawCommentText);
+
+        findAnnotations(t, method);
+        extractParameters(t, method);
+
+        Matcher matcher = GETTER_METHOD_NAME.matcher(name);
+        if (matcher.matches()) {
+            int startName = matcher.start(2);
+            String propName = name.substring(startName, startName + 1).toLowerCase() + name.substring(startName + 1);
+            PropertyMetaData property = getCurrentClass().addReadableProperty(propName, returnType, rawCommentText, method);
+            for (String annotation : method.getAnnotationTypeNames()) {
+                property.addAnnotationTypeName(annotation);
+            }
+            return;
+        }
+
+        if (method.getParameters().size() != 1) {
+            return;
+        }
+        matcher = SETTER_METHOD_NAME.matcher(name);
+        if (matcher.matches()) {
+            int startName = matcher.start(1);
+            String propName = name.substring(startName, startName + 1).toLowerCase() + name.substring(startName + 1);
+            TypeMetaData type = method.getParameters().get(0).getType();
+            getCurrentClass().addWriteableProperty(propName, type, rawCommentText, method);
+        }
+    }
+
+    private void extractParameters(GroovySourceAST t, MethodMetaData method) {
+        GroovySourceAST paramsAst = t.childOfType(PARAMETERS);
+        for (
+                GroovySourceAST child = (GroovySourceAST) paramsAst.getFirstChild(); child != null;
+                child = (GroovySourceAST) child.getNextSibling()) {
+            assert child.getType() == PARAMETER_DEF || child.getType() == VARIABLE_PARAMETER_DEF;
+            TypeMetaData type = extractTypeName((GroovySourceAST) child.getFirstChild().getNextSibling());
+            if (child.getType() == VARIABLE_PARAMETER_DEF) {
+                type.setVarargs();
+            }
+            method.addParameter(extractIdent(child), type);
+        }
+    }
+
+    @Override
+    public void visitVariableDef(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            maybeAddPropertyFromField(t);
+            skipJavaDocComment(t);
+        }
+    }
+
+    private void maybeAddPropertyFromField(GroovySourceAST t) {
+        GroovySourceAST parentNode = getParentNode();
+        boolean isField = parentNode != null && parentNode.getType() == OBJBLOCK;
+        if (!isField) {
+            return;
+        }
+
+        int modifiers = extractModifiers(t);
+        boolean isConst = getCurrentClass().isInterface() || (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
+        if (isConst) {
+            visitConst(t);
+            return;
+        }
+
+        boolean isProp = groovy && !Modifier.isStatic(modifiers) && !Modifier.isPublic(modifiers)
+                && !Modifier.isProtected(modifiers) && !Modifier.isPrivate(modifiers);
+        if (!isProp) {
+            return;
+        }
+
+        ASTIterator children = new ASTIterator(t);
+        children.skip(MODIFIERS);
+
+        String propertyName = extractIdent(t);
+        TypeMetaData propertyType = extractTypeName(children.current);
+        ClassMetaData currentClass = getCurrentClass();
+
+        MethodMetaData getterMethod = currentClass.addMethod(String.format("get%s", StringUtils.capitalize(
+                propertyName)), propertyType, "");
+        PropertyMetaData property = currentClass.addReadableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), getterMethod);
+        findAnnotations(t, property);
+        if (!Modifier.isFinal(modifiers)) {
+            MethodMetaData setterMethod = currentClass.addMethod(String.format("set%s", StringUtils.capitalize(
+                    propertyName)), TypeMetaData.VOID, "");
+            setterMethod.addParameter(propertyName, propertyType);
+            currentClass.addWriteableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), setterMethod);
+        }
+    }
+
+    private void visitConst(GroovySourceAST t) {
+        String constName = extractIdent(t);
+        GroovySourceAST assign = t.childOfType(ASSIGN);
+        String value = null;
+        if (assign != null) {
+            value = extractLiteral(assign.getFirstChild());
+        }
+        getCurrentClass().getConstants().put(constName, value);
+    }
+
+    private String extractLiteral(AST ast) {
+        switch (ast.getType()) {
+            case EXPR:
+                // The java grammar wraps initialisers in an EXPR token
+                return extractLiteral(ast.getFirstChild());
+            case NUM_INT:
+            case NUM_LONG:
+            case NUM_FLOAT:
+            case NUM_DOUBLE:
+            case NUM_BIG_INT:
+            case NUM_BIG_DECIMAL:
+            case STRING_LITERAL:
+                return ast.getText();
+        }
+        return null;
+    }
+
+    public GroovySourceAST pop() {
+        if (!parseStack.isEmpty()) {
+            GroovySourceAST ast = parseStack.removeFirst();
+            ClassMetaData classMetaData = typeTokens.remove(ast);
+            if (classMetaData != null) {
+                assert classMetaData == classStack.getFirst();
+                classStack.removeFirst();
+            }
+            return ast;
+        }
+        return null;
+    }
+
+    @Override
+    public void push(GroovySourceAST t) {
+        parseStack.addFirst(t);
+    }
+
+    private GroovySourceAST getParentNode() {
+        if (parseStack.size() > 1) {
+            return parseStack.get(1);
+        }
+        return null;
+    }
+
+    private int extractModifiers(GroovySourceAST ast) {
+        GroovySourceAST modifiers = ast.childOfType(MODIFIERS);
+        if (modifiers == null) {
+            return 0;
+        }
+        int modifierFlags = 0;
+        for (
+                GroovySourceAST child = (GroovySourceAST) modifiers.getFirstChild(); child != null;
+                child = (GroovySourceAST) child.getNextSibling()) {
+            switch (child.getType()) {
+                case LITERAL_private:
+                    modifierFlags |= Modifier.PRIVATE;
+                    break;
+                case LITERAL_protected:
+                    modifierFlags |= Modifier.PROTECTED;
+                    break;
+                case LITERAL_public:
+                    modifierFlags |= Modifier.PUBLIC;
+                    break;
+                case FINAL:
+                    modifierFlags |= Modifier.FINAL;
+                    break;
+                case LITERAL_static:
+                    modifierFlags |= Modifier.STATIC;
+                    break;
+            }
+        }
+        return modifierFlags;
+    }
+
+    private TypeMetaData extractTypeName(GroovySourceAST ast) {
+        TypeMetaData type = new TypeMetaData();
+        switch (ast.getType()) {
+            case TYPE:
+                GroovySourceAST typeName = (GroovySourceAST) ast.getFirstChild();
+                extractTypeName(typeName, type);
+                break;
+            case WILDCARD_TYPE:
+                // In the groovy grammar, the bounds are sibling of the ?, in the java grammar, they are the child
+                GroovySourceAST bounds = (GroovySourceAST) (groovy ? ast.getNextSibling() : ast.getFirstChild());
+                if (bounds == null) {
+                    type.setWildcard();
+                } else if (bounds.getType() == TYPE_UPPER_BOUNDS) {
+                    type.setUpperBounds(extractTypeName((GroovySourceAST) bounds.getFirstChild()));
+                } else if (bounds.getType() == TYPE_LOWER_BOUNDS) {
+                    type.setLowerBounds(extractTypeName((GroovySourceAST) bounds.getFirstChild()));
+                }
+                break;
+            case IDENT:
+            case DOT:
+                extractTypeName(ast, type);
+                break;
+            default:
+                throw new RuntimeException(String.format("Unexpected token in type name: %s", ast));
+        }
+
+        return type;
+    }
+
+    private void extractTypeName(GroovySourceAST ast, TypeMetaData type) {
+        if (ast == null) {
+            type.setName("java.lang.Object");
+            return;
+        }
+        switch (ast.getType()) {
+            case LITERAL_boolean:
+                type.setName("boolean");
+                return;
+            case LITERAL_byte:
+                type.setName("byte");
+                return;
+            case LITERAL_char:
+                type.setName("char");
+                return;
+            case LITERAL_double:
+                type.setName("double");
+                return;
+            case LITERAL_float:
+                type.setName("float");
+                return;
+            case LITERAL_int:
+                type.setName("int");
+                return;
+            case LITERAL_long:
+                type.setName("long");
+                return;
+            case LITERAL_void:
+                type.setName("void");
+                return;
+            case ARRAY_DECLARATOR:
+                extractTypeName((GroovySourceAST) ast.getFirstChild(), type);
+                type.addArrayDimension();
+                return;
+        }
+
+        type.setName(extractName(ast));
+        GroovySourceAST typeArgs = ast.childOfType(TYPE_ARGUMENTS);
+        if (typeArgs != null) {
+            for (
+                    GroovySourceAST child = (GroovySourceAST) typeArgs.getFirstChild(); child != null;
+                    child = (GroovySourceAST) child.getNextSibling()) {
+                assert child.getType() == TYPE_ARGUMENT;
+                type.addTypeArg(extractTypeName((GroovySourceAST) child.getFirstChild()));
+            }
+        }
+    }
+
+    private void skipJavaDocComment(GroovySourceAST t) {
+        lastLineCol = new LineColumn(t.getLine(), t.getColumn());
+    }
+
+    private String getJavaDocCommentsBeforeNode(GroovySourceAST t) {
+        String result = "";
+        LineColumn thisLineCol = new LineColumn(t.getLine(), t.getColumn());
+        String text = sourceBuffer.getSnippet(lastLineCol, thisLineCol);
+        if (text != null) {
+            Matcher m = PREV_JAVADOC_COMMENT_PATTERN.matcher(text);
+            if (m.find()) {
+                result = m.group(1);
+            }
+        }
+        lastLineCol = thisLineCol;
+        return result;
+    }
+
+    private void findAnnotations(GroovySourceAST t, AbstractLanguageElement currentElement) {
+        GroovySourceAST modifiers = t.childOfType(MODIFIERS);
+        if (modifiers != null) {
+            List<GroovySourceAST> children = modifiers.childrenOfType(ANNOTATION);
+            for (GroovySourceAST child : children) {
+                String identifier = extractIdent(child);
+                currentElement.addAnnotationTypeName(identifier);
+            }
+        }
+    }
+
+    private String extractIdent(GroovySourceAST t) {
+        return t.childOfType(IDENT).getText();
+    }
+
+    private String extractName(GroovySourceAST t) {
+        if (t.getType() == DOT) {
+            GroovySourceAST firstChild = (GroovySourceAST) t.getFirstChild();
+            GroovySourceAST secondChild = (GroovySourceAST) firstChild.getNextSibling();
+            return extractName(firstChild) + "." + extractName(secondChild);
+        }
+        if (t.getType() == IDENT) {
+            return t.getText();
+        }
+        if (t.getType() == STAR) {
+            return t.getText();
+        }
+
+        GroovySourceAST child = t.childOfType(DOT);
+        if (child != null) {
+            return extractName(child);
+        }
+        child = t.childOfType(IDENT);
+        if (child != null) {
+            return extractName(child);
+        }
+
+        throw new RuntimeException(String.format("Unexpected token in name: %s", t));
+    }
+
+    private static class ASTIterator {
+        GroovySourceAST current;
+
+        private ASTIterator(GroovySourceAST parent) {
+            this.current = (GroovySourceAST) parent.getFirstChild();
+        }
+
+        void skip(int token) {
+            if (current != null && current.getType() == token) {
+                current = (GroovySourceAST) current.getNextSibling();
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/TypeNameResolver.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/TypeNameResolver.java
new file mode 100644
index 0000000..98841e4
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/TypeNameResolver.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Action;
+import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+import org.gradle.build.docs.dsl.source.model.TypeMetaData;
+import org.gradle.build.docs.model.ClassMetaDataRepository;
+import org.gradle.internal.UncheckedException;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Resolves partial type names into fully qualified type names.
+ */
+public class TypeNameResolver {
+    private final Set<String> primitiveTypes = new HashSet<String>();
+    private final List<String> groovyImplicitImportPackages = new ArrayList<String>();
+    private final List<String> groovyImplicitTypes = new ArrayList<String>();
+    private final ClassMetaDataRepository<ClassMetaData> metaDataRepository;
+
+    public TypeNameResolver(ClassMetaDataRepository<ClassMetaData> metaDataRepository) {
+        this.metaDataRepository = metaDataRepository;
+        primitiveTypes.add("boolean");
+        primitiveTypes.add("byte");
+        primitiveTypes.add("char");
+        primitiveTypes.add("short");
+        primitiveTypes.add("int");
+        primitiveTypes.add("long");
+        primitiveTypes.add("float");
+        primitiveTypes.add("double");
+        primitiveTypes.add("void");
+        groovyImplicitImportPackages.add("java.util.");
+        groovyImplicitImportPackages.add("java.io.");
+        groovyImplicitImportPackages.add("java.net.");
+        groovyImplicitImportPackages.add("groovy.lang.");
+        groovyImplicitImportPackages.add("groovy.util.");
+        groovyImplicitTypes.add("java.math.BigDecimal");
+        groovyImplicitTypes.add("java.math.BigInteger");
+
+        // check that groovy is visible.
+        try {
+            getClass().getClassLoader().loadClass("groovy.lang.Closure");
+        } catch (ClassNotFoundException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    /**
+     * Resolves the names in the given type into fully qualified names.
+     */
+    public void resolve(final TypeMetaData type, final ClassMetaData classMetaData) {
+        type.visitTypes(new Action<TypeMetaData>() {
+            public void execute(TypeMetaData t) {
+                t.setName(resolve(t.getName(), classMetaData));
+            }
+        });
+    }
+
+    /**
+     * Resolves a source type name into a fully qualified type name.
+     */
+    public String resolve(String name, ClassMetaData classMetaData) {
+        if (primitiveTypes.contains(name)) {
+            return name;
+        }
+
+        String candidateClassName;
+        String[] innerNames = name.split("\\.");
+        ClassMetaData pos = classMetaData;
+        for (int i = 0; i < innerNames.length; i++) {
+            String innerName = innerNames[i];
+            candidateClassName = pos.getClassName() + '.' + innerName;
+            if (!pos.getInnerClassNames().contains(candidateClassName)) {
+                break;
+            }
+            if (i == innerNames.length - 1) {
+                return candidateClassName;
+            }
+            pos = metaDataRepository.get(candidateClassName);
+        }
+
+        String outerClassName = classMetaData.getOuterClassName();
+        while (outerClassName != null) {
+            if (name.equals(StringUtils.substringAfterLast(outerClassName, "."))) {
+                return outerClassName;
+            }
+            ClassMetaData outerClass = metaDataRepository.get(outerClassName);
+            candidateClassName = outerClassName + '.' + name;
+            if (outerClass.getInnerClassNames().contains(candidateClassName)) {
+                return candidateClassName;
+            }
+            outerClassName = outerClass.getOuterClassName();
+        }
+
+        if (name.contains(".")) {
+            return name;
+        }
+
+        for (String importedClass : classMetaData.getImports()) {
+            String baseName = StringUtils.substringAfterLast(importedClass, ".");
+            if (baseName.equals("*")) {
+                candidateClassName = StringUtils.substringBeforeLast(importedClass, ".") + "." + name;
+                if (metaDataRepository.find(candidateClassName) != null) {
+                    return candidateClassName;
+                }
+                if (importedClass.startsWith("java.") && isVisibleClass(candidateClassName)) {
+                    return candidateClassName;
+                }
+            } else if (name.equals(baseName)) {
+                return importedClass;
+            }
+        }
+
+        candidateClassName = classMetaData.getPackageName() + "." + name;
+        if (metaDataRepository.find(candidateClassName) != null) {
+            return candidateClassName;
+        }
+
+        candidateClassName = "java.lang." + name;
+        if (isVisibleClass(candidateClassName)) {
+            return candidateClassName;
+        }
+
+        if (classMetaData.isGroovy()) {
+            candidateClassName = "java.math." + name;
+            if (groovyImplicitTypes.contains(candidateClassName)) {
+                return candidateClassName;
+            }
+            for (String prefix : groovyImplicitImportPackages) {
+                candidateClassName = prefix + name;
+                if (isVisibleClass(candidateClassName)) {
+                    return candidateClassName;
+                }
+            }
+        }
+
+        return name;
+    }
+
+    private boolean isVisibleClass(String candidateClassName) {
+        try {
+            getClass().getClassLoader().loadClass(candidateClassName);
+            return true;
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+        return false;
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java
new file mode 100644
index 0000000..4f9c5a7
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source.model;
+
+import org.gradle.api.Transformer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractLanguageElement implements LanguageElement, Serializable {
+    private String rawCommentText;
+    private final List<String> annotationNames = new ArrayList<String>();
+
+    protected AbstractLanguageElement() {
+    }
+
+    protected AbstractLanguageElement(String rawCommentText) {
+        this.rawCommentText = rawCommentText;
+    }
+
+    public String getRawCommentText() {
+        return rawCommentText;
+    }
+
+    public void setRawCommentText(String rawCommentText) {
+        this.rawCommentText = rawCommentText;
+    }
+
+    public List<String> getAnnotationTypeNames() {
+        return annotationNames;
+    }
+
+    public void addAnnotationTypeName(String annotationType) {
+        annotationNames.add(annotationType);
+    }
+
+    public boolean isDeprecated() {
+        return annotationNames.contains(Deprecated.class.getName());
+    }
+
+    public boolean isIncubating() {
+        return annotationNames.contains("org.gradle.api.Incubating");
+    }
+
+    public void resolveTypes(Transformer<String, String> transformer) {
+        for (int i = 0; i < annotationNames.size(); i++) {
+            annotationNames.set(i, transformer.transform(annotationNames.get(i)));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java
new file mode 100644
index 0000000..dab28f9
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source.model;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.build.docs.model.Attachable;
+import org.gradle.build.docs.model.ClassMetaDataRepository;
+import org.gradle.util.GUtil;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Static meta-data about a class extracted from the source for the class.
+ */
+public class ClassMetaData extends AbstractLanguageElement implements Serializable, Attachable<ClassMetaData>, TypeContainer {
+    private final String className;
+    private String superClassName;
+    private final String packageName;
+    private final boolean isInterface;
+    private final boolean isGroovy;
+    private final List<String> imports = new ArrayList<String>();
+    private final List<String> interfaceNames = new ArrayList<String>();
+    private final Map<String, PropertyMetaData> declaredProperties = new HashMap<String, PropertyMetaData>();
+    private final Set<MethodMetaData> declaredMethods = new HashSet<MethodMetaData>();
+    private final List<String> innerClassNames = new ArrayList<String>();
+    private String outerClassName;
+    private transient ClassMetaDataRepository<ClassMetaData> metaDataRepository;
+    public final HashMap<String, String> constants = new HashMap<String, String>();
+
+    public ClassMetaData(String className, String packageName, boolean isInterface, boolean isGroovy, String rawClassComment) {
+        super(rawClassComment);
+        this.className = className;
+        this.packageName = packageName;
+        this.isInterface = isInterface;
+        this.isGroovy = isGroovy;
+    }
+
+    public ClassMetaData(String className) {
+        this(className, StringUtils.substringBeforeLast(className, "."), false, false, "");
+    }
+
+    @Override
+    public String toString() {
+        return className;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public String getSimpleName() {
+        return StringUtils.substringAfterLast(className, ".");
+    }
+
+    public String getPackageName() {
+        return packageName;
+    }
+
+    public boolean isInterface() {
+        return isInterface;
+    }
+
+    public boolean isGroovy() {
+        return isGroovy;
+    }
+
+    public String getSuperClassName() {
+        return superClassName;
+    }
+
+    public void setSuperClassName(String superClassName) {
+        this.superClassName = superClassName;
+    }
+
+    public ClassMetaData getSuperClass() {
+        return superClassName == null ? null : metaDataRepository.find(superClassName);
+    }
+
+    public List<String> getInterfaceNames() {
+        return interfaceNames;
+    }
+
+    public void addInterfaceName(String name) {
+        interfaceNames.add(name);
+    }
+
+    public List<ClassMetaData> getInterfaces() {
+        List<ClassMetaData> interfaces = new ArrayList<ClassMetaData>();
+        for (String interfaceName : interfaceNames) {
+            ClassMetaData interfaceMetaData = metaDataRepository.find(interfaceName);
+            if (interfaceMetaData != null) {
+                interfaces.add(interfaceMetaData);
+            }
+        }
+        return interfaces;
+    }
+
+    public List<String> getInnerClassNames() {
+        return innerClassNames;
+    }
+
+    public void addInnerClassName(String innerClassName) {
+        innerClassNames.add(innerClassName);
+    }
+
+    public String getOuterClassName() {
+        return outerClassName;
+    }
+
+    public void setOuterClassName(String outerClassName) {
+        this.outerClassName = outerClassName;
+    }
+
+    public List<String> getImports() {
+        return imports;
+    }
+
+    public void addImport(String importName) {
+        imports.add(importName);
+    }
+
+    public PropertyMetaData addReadableProperty(String name, TypeMetaData type, String rawCommentText, MethodMetaData getterMethod) {
+        PropertyMetaData property = getProperty(name);
+        property.setType(type);
+        property.setRawCommentText(rawCommentText);
+        property.setGetter(getterMethod);
+        return property;
+    }
+
+    public PropertyMetaData addWriteableProperty(String name, TypeMetaData type, String rawCommentText, MethodMetaData setterMethod) {
+        PropertyMetaData property = getProperty(name);
+        if (property.getType() == null) {
+            property.setType(type);
+        }
+        if (!GUtil.isTrue(property.getRawCommentText())) {
+            property.setRawCommentText(rawCommentText);
+        }
+        property.setSetter(setterMethod);
+        return property;
+    }
+
+    public PropertyMetaData findDeclaredProperty(String name) {
+        return declaredProperties.get(name);
+    }
+
+    public Set<String> getDeclaredPropertyNames() {
+        return declaredProperties.keySet();
+    }
+
+    public Set<PropertyMetaData> getDeclaredProperties() {
+        return new HashSet<PropertyMetaData>(declaredProperties.values());
+    }
+
+    public Set<MethodMetaData> getDeclaredMethods() {
+        return declaredMethods;
+    }
+
+    public Set<String> getDeclaredMethodNames() {
+        Set<String> names = new HashSet<String>();
+        for (MethodMetaData declaredMethod : declaredMethods) {
+            names.add(declaredMethod.getName());
+        }
+        return names;
+    }
+
+    public MethodMetaData findDeclaredMethod(String signature) {
+        for (MethodMetaData method : declaredMethods) {
+            if (method.getOverrideSignature().equals(signature)) {
+                return method;
+            }
+        }
+        return null;
+    }
+
+    public List<MethodMetaData> findDeclaredMethods(String name) {
+        List<MethodMetaData> methods = new ArrayList<MethodMetaData>();
+        for (MethodMetaData method : declaredMethods) {
+            if (method.getName().equals(name)) {
+                methods.add(method);
+            }
+        }
+        return methods;
+    }
+
+    /**
+     * Finds a property by name. Includes inherited properties.
+     *
+     * @param name The property name.
+     * @return The property, or null if no such property exists.
+     */
+    public PropertyMetaData findProperty(String name) {
+        PropertyMetaData propertyMetaData = declaredProperties.get(name);
+        if (propertyMetaData != null) {
+            return propertyMetaData;
+        }
+        ClassMetaData superClass = getSuperClass();
+        if (superClass != null) {
+            return superClass.findProperty(name);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the set of property names for this class, including inherited properties.
+     *
+     * @return The set of property names.
+     */
+    public Set<String> getPropertyNames() {
+        Set<String> propertyNames = new TreeSet<String>();
+        propertyNames.addAll(declaredProperties.keySet());
+        ClassMetaData superClass = getSuperClass();
+        if (superClass != null) {
+            propertyNames.addAll(superClass.getPropertyNames());
+        }
+        return propertyNames;
+    }
+
+    private PropertyMetaData getProperty(String name) {
+        PropertyMetaData property = declaredProperties.get(name);
+        if (property == null) {
+            property = new PropertyMetaData(name, this);
+            declaredProperties.put(name, property);
+        }
+        return property;
+    }
+
+    public Map<String, String> getConstants() {
+        return constants;
+    }
+    
+    public void attach(ClassMetaDataRepository<ClassMetaData> metaDataRepository) {
+        this.metaDataRepository = metaDataRepository;
+    }
+
+    public MethodMetaData addMethod(String name, TypeMetaData returnType, String rawCommentText) {
+        MethodMetaData method = new MethodMetaData(name, this);
+        declaredMethods.add(method);
+        method.setReturnType(returnType);
+        method.setRawCommentText(rawCommentText);
+        return method;
+    }
+
+    public void resolveTypes(Transformer<String, String> transformer) {
+        super.resolveTypes(transformer);
+        if (superClassName != null) {
+            superClassName = transformer.transform(superClassName);
+        }
+        for (int i = 0; i < interfaceNames.size(); i++) {
+            interfaceNames.set(i, transformer.transform(interfaceNames.get(i)));
+        }
+        for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
+            propertyMetaData.resolveTypes(transformer);
+        }
+        for (MethodMetaData methodMetaData : declaredMethods) {
+            methodMetaData.resolveTypes(transformer);
+        }
+    }
+
+    public void visitTypes(Action<TypeMetaData> action) {
+        for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
+            propertyMetaData.visitTypes(action);
+        }
+        for (MethodMetaData methodMetaData : declaredMethods) {
+            methodMetaData.visitTypes(action);
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/LanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/LanguageElement.java
new file mode 100644
index 0000000..12ecdf4
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/LanguageElement.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.build.docs.dsl.source.model;
+
+import java.util.List;
+
+public interface LanguageElement {
+    String getRawCommentText();
+
+    List<String> getAnnotationTypeNames();
+
+    boolean isDeprecated();
+
+    boolean isIncubating();
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaData.java
new file mode 100644
index 0000000..9d00fc0
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaData.java
@@ -0,0 +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.build.docs.dsl.source.model;
+
+import org.gradle.api.Action;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Static meta-data about a method extracted from the source for the class.
+ */
+public class MethodMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
+    private final String name;
+    private final ClassMetaData ownerClass;
+    private final List<ParameterMetaData> parameters = new ArrayList<ParameterMetaData>();
+    private TypeMetaData returnType;
+
+    public MethodMetaData(String name, ClassMetaData ownerClass) {
+        this.name = name;
+        this.ownerClass = ownerClass;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s.%s()", ownerClass, name);
+    }
+
+    public ClassMetaData getOwnerClass() {
+        return ownerClass;
+    }
+
+    public TypeMetaData getReturnType() {
+        return returnType;
+    }
+
+    public void setReturnType(TypeMetaData returnType) {
+        this.returnType = returnType;
+    }
+
+    public MethodMetaData getOverriddenMethod() {
+        LinkedList<ClassMetaData> queue = new LinkedList<ClassMetaData>();
+        queue.add(ownerClass.getSuperClass());
+        queue.addAll(ownerClass.getInterfaces());
+        
+        String overrideSignature = getOverrideSignature();
+
+        while (!queue.isEmpty()) {
+            ClassMetaData cl = queue.removeFirst();
+            if (cl == null) {
+                continue;
+            }
+            MethodMetaData overriddenMethod = cl.findDeclaredMethod(overrideSignature);
+            if (overriddenMethod != null) {
+                return overriddenMethod;
+            }
+            queue.add(cl.getSuperClass());
+            queue.addAll(cl.getInterfaces());
+        }
+
+        return null;
+    }
+
+    public List<ParameterMetaData> getParameters() {
+        return parameters;
+    }
+
+    public ParameterMetaData addParameter(String name, TypeMetaData type) {
+        ParameterMetaData param = new ParameterMetaData(name);
+        param.setType(type);
+        parameters.add(param);
+        return param;
+    }
+
+    public String getSignature() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(returnType.getSignature());
+        builder.append(' ');
+        builder.append(name);
+        builder.append('(');
+        for (int i = 0; i < parameters.size(); i++) {
+            ParameterMetaData param =  parameters.get(i);
+            if (i > 0) {
+                builder.append(", ");
+            }
+            builder.append(param.getSignature());
+        }
+        builder.append(')');
+        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);
+        builder.append('(');
+        for (int i = 0; i < parameters.size(); i++) {
+            ParameterMetaData param =  parameters.get(i);
+            if (i > 0) {
+                builder.append(", ");
+            }
+            builder.append(param.getType().getRawType().getSignature());
+        }
+        builder.append(')');
+        return builder.toString();
+    }
+
+    public void visitTypes(Action<TypeMetaData> action) {
+        action.execute(returnType);
+        for (ParameterMetaData parameter : parameters) {
+            parameter.visitTypes(action);
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaData.java
new file mode 100644
index 0000000..e7668b3
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaData.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.build.docs.dsl.source.model;
+
+import org.gradle.api.Action;
+
+import java.io.Serializable;
+
+/**
+ * Static meta-data about a method parameter extracted from the source for the method.
+ */
+public class ParameterMetaData implements Serializable, TypeContainer {
+    private final String name;
+    private TypeMetaData type;
+
+    public ParameterMetaData(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TypeMetaData getType() {
+        return type;
+    }
+
+    public void setType(TypeMetaData type) {
+        this.type = type;
+    }
+
+    public String getSignature() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(type.getSignature());
+        builder.append(" ");
+        builder.append(name);
+        return builder.toString();
+    }
+
+    public void visitTypes(Action<TypeMetaData> action) {
+        action.execute(type);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java
new file mode 100644
index 0000000..dc8230b
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java
@@ -0,0 +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.build.docs.dsl.source.model;
+
+import org.gradle.api.Action;
+
+import java.io.Serializable;
+
+/**
+ * Static meta-data about a property extracted from the source for the class.
+ */
+public class PropertyMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
+    private TypeMetaData type;
+    private final String name;
+    private final ClassMetaData ownerClass;
+    private MethodMetaData setter;
+    private MethodMetaData getter;
+
+    public PropertyMetaData(String name, ClassMetaData ownerClass) {
+        this.name = name;
+        this.ownerClass = ownerClass;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s.%s", ownerClass, name);
+    }
+
+    public TypeMetaData getType() {
+        return type;
+    }
+
+    public void setType(TypeMetaData type) {
+        this.type = type;
+    }
+
+    public boolean isWriteable() {
+        return setter != null;
+    }
+
+    public ClassMetaData getOwnerClass() {
+        return ownerClass;
+    }
+
+    public String getSignature() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(type.getSignature());
+        builder.append(' ');
+        builder.append(name);
+        return builder.toString();
+    }
+
+    public MethodMetaData getGetter() {
+        return getter;
+    }
+
+    public void setGetter(MethodMetaData getter) {
+        this.getter = getter;
+    }
+
+    public MethodMetaData getSetter() {
+        return setter;
+    }
+
+    public void setSetter(MethodMetaData setter) {
+        this.setter = setter;
+    }
+
+    public PropertyMetaData getOverriddenProperty() {
+        MethodMetaData overriddenMethod = null;
+        if (getter != null) {
+            overriddenMethod = getter.getOverriddenMethod();
+        }
+        if (overriddenMethod == null && setter != null) {
+            overriddenMethod = setter.getOverriddenMethod();
+        }
+        if (overriddenMethod != null) {
+            return overriddenMethod.getOwnerClass().findDeclaredProperty(name);
+        }
+
+        return null;
+    }
+
+    public void visitTypes(Action<TypeMetaData> action) {
+        action.execute(type);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeContainer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeContainer.java
new file mode 100644
index 0000000..f916c27
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeContainer.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.build.docs.dsl.source.model;
+
+import org.gradle.api.Action;
+
+public interface TypeContainer {
+    void visitTypes(Action<TypeMetaData> action);
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaData.java
new file mode 100644
index 0000000..76a64b7
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaData.java
@@ -0,0 +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.build.docs.dsl.source.model;
+
+import org.gradle.api.Action;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Static meta-data about a type reference extracted from source.
+ */
+public class TypeMetaData implements Serializable, TypeContainer {
+    public static final TypeMetaData VOID = new TypeMetaData("void");
+    public static final TypeMetaData OBJECT = new TypeMetaData("java.lang.Object");
+
+    private String name;
+    private int arrayDimensions;
+    private boolean varargs;
+    private List<TypeMetaData> typeArgs;
+    private boolean wildcard;
+    private TypeMetaData upperBounds;
+    private TypeMetaData lowerBounds;
+
+    public TypeMetaData(String name) {
+        this.name = name;
+    }
+
+    public TypeMetaData() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getArrayDimensions() {
+        return arrayDimensions + (varargs ? 1 : 0);
+    }
+
+    public TypeMetaData addArrayDimension() {
+        arrayDimensions++;
+        return this;
+    }
+
+    public boolean isVarargs() {
+        return varargs;
+    }
+
+    public TypeMetaData setVarargs() {
+        this.varargs = true;
+        return this;
+    }
+
+    public List<TypeMetaData> getTypeArgs() {
+        return typeArgs;
+    }
+
+    public TypeMetaData getRawType() {
+        if (wildcard || lowerBounds != null) {
+            return OBJECT;
+        }
+        if (upperBounds != null) {
+            return upperBounds.getRawType();
+        }
+        TypeMetaData rawType = new TypeMetaData(name);
+        rawType.arrayDimensions = arrayDimensions;
+        rawType.varargs = varargs;
+        return rawType;
+    }
+
+    public String getSignature() {
+        final StringBuilder builder = new StringBuilder();
+
+        visitSignature(new SignatureVisitor() {
+            public void visitText(String text) {
+                builder.append(text);
+            }
+
+            public void visitType(String name) {
+                builder.append(name);
+            }
+        });
+        return builder.toString();
+    }
+
+    public String getArraySuffix() {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < arrayDimensions; i++) {
+            builder.append("[]");
+        }
+        if (varargs) {
+            builder.append("...");
+        }
+        return builder.toString();
+    }
+
+    public TypeMetaData addTypeArg(TypeMetaData typeArg) {
+        if (typeArgs == null) {
+            typeArgs = new ArrayList<TypeMetaData>();
+        }
+        typeArgs.add(typeArg);
+        return this;
+    }
+
+    public void visitTypes(Action<TypeMetaData> action) {
+        if (wildcard) {
+            return;
+        }
+        if (upperBounds != null) {
+            upperBounds.visitTypes(action);
+            return;
+        }
+        if (lowerBounds != null) {
+            lowerBounds.visitTypes(action);
+            return;
+        }
+        
+        action.execute(this);
+        if (typeArgs != null) {
+            for (TypeMetaData typeArg : typeArgs) {
+                typeArg.visitTypes(action);
+            }
+        }
+    }
+
+    public void visitSignature(SignatureVisitor visitor) {
+        if (wildcard) {
+            visitor.visitText("?");
+        } else if (upperBounds != null) {
+            visitor.visitText("? extends ");
+            upperBounds.visitSignature(visitor);
+        } else if (lowerBounds != null) {
+            visitor.visitText("? super ");
+            lowerBounds.visitSignature(visitor);
+        } else {
+            visitor.visitType(name);
+            if (typeArgs != null) {
+                visitor.visitText("<");
+                for (int i = 0; i < typeArgs.size(); i++) {
+                    if (i > 0) {
+                        visitor.visitText(", ");
+                    }
+                    TypeMetaData typeArg = typeArgs.get(i);
+                    typeArg.visitSignature(visitor);
+                }
+                visitor.visitText(">");
+            }
+            String suffix = getArraySuffix();
+            if (suffix.length() > 0) {
+                visitor.visitText(suffix);
+            }
+        }
+    }
+
+    public TypeMetaData setWildcard() {
+        wildcard = true;
+        return this;
+    }
+
+    public TypeMetaData setUpperBounds(TypeMetaData upperBounds) {
+        this.upperBounds = upperBounds;
+        return this;
+    }
+
+    public TypeMetaData setLowerBounds(TypeMetaData lowerBounds) {
+        this.lowerBounds = lowerBounds;
+        return this;
+    }
+
+    public interface SignatureVisitor {
+        void visitText(String text);
+
+        void visitType(String name);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepository.java b/buildSrc/src/main/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepository.java
index 43d8936..f5ec24e 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepository.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/model/SimpleClassMetaDataRepository.java
@@ -26,6 +26,7 @@ import java.util.Map;
 public class SimpleClassMetaDataRepository<T extends Attachable<T>> implements ClassMetaDataRepository<T> {
     private final Map<String, T> classes = new HashMap<String, T>();
 
+    @SuppressWarnings("unchecked")
     public void load(File repoFile) {
         try {
             FileInputStream inputStream = new FileInputStream(repoFile);
diff --git a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy
deleted file mode 100644
index 1cc1658..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.integtest
-
-import org.gradle.api.Project
-
-class IntegTestConvention {
-    private final Project project
-    final List integTests = []
-
-    IntegTestConvention(Project project) {
-        this.project = project
-    }
-
-    String getIntegTestMode() {
-        if (!project.tasks.findByName('ciBuild') || !project.gradle.taskGraph.populated) {
-            return null
-        }
-        if (project.isCIBuild()) {
-            return 'forking'
-        }
-        return System.getProperty("org.gradle.integtest.executer") ?: 'embedded'
-    }
-
-    File getIntegTestUserDir() {
-        return project.file('intTestHomeDir')
-    }
-
-    File getIntegTestImageDir() {
-        if (!project.tasks.findByName('intTestImage')) {
-            return null
-        }
-        return project.intTestImage.destinationDir
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy
deleted file mode 100644
index fd6abe3..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.integtest
-
-import org.gradle.api.Project
-import org.gradle.api.Plugin
-
-class IntegTestPlugin implements Plugin<Project> {
-    public void apply(Project project) {
-        project.convention.plugins.integTest = new IntegTestConvention(project)
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
deleted file mode 100644
index 13f93cb..0000000
--- a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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/test/groovy/org/gradle/build/docs/XmlSpecification.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/XmlSpecification.groovy
index a0ec953..1c5f47b 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/XmlSpecification.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/XmlSpecification.groovy
@@ -25,11 +25,12 @@ import org.w3c.dom.*
 abstract class XmlSpecification extends Specification {
     final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
 
-    def parse(String str) {
+    def parse(String str, Document document = null) {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
         factory.setNamespaceAware(true)
         DocumentBuilder builder = factory.newDocumentBuilder()
-        return builder.parse(new InputSource(new StringReader(str))).documentElement
+        def parsed = builder.parse(new InputSource(new StringReader(str))).documentElement
+        return document ? document.importNode(parsed, true) : parsed
     }
 
     def formatTree(Closure cl) {
@@ -81,6 +82,10 @@ abstract class XmlSpecification extends Specification {
                 target.append(" $attr.name=\"$attr.value\"")
             }
 
+            element.childNodes.findAll { it instanceof Text }.each {
+                assert it.textContent != null : "Found null text element in <$element.tagName>"
+            }
+
             List<Node> trimmedContent = element.childNodes.collect { it };
             boolean inlineContent = trimmedContent.find { it instanceof Text && it.textContent.trim() }
 
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
deleted file mode 100644
index e4d5301..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
+++ /dev/null
@@ -1,717 +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
-
-import org.gradle.api.Project
-import org.gradle.build.docs.dsl.model.ClassMetaData
-import org.gradle.build.docs.model.SimpleClassMetaDataRepository
-import org.gradle.testfixtures.ProjectBuilder
-import spock.lang.Specification
-
-class ExtractDslMetaDataTaskTest extends Specification {
-    final Project project = new ProjectBuilder().build()
-    final ExtractDslMetaDataTask task = project.tasks.add('dsl', ExtractDslMetaDataTask.class)
-    final SimpleClassMetaDataRepository<ClassMetaData> repository = new SimpleClassMetaDataRepository<ClassMetaData>()
-
-    def setup() {
-        task.destFile = project.file('meta-data.bin')
-    }
-
-    def extractsClassMetaDataFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClass.groovy')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/A.groovy')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-        task.source testFile('org/gradle/test/Interface1.java')
-        task.source testFile('org/gradle/test/Interface2.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClass')
-        groovyClass.groovy
-        !groovyClass.isInterface()
-        groovyClass.rawCommentText.contains('This is a groovy class.')
-        groovyClass.superClassName == 'org.gradle.test.A'
-        groovyClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
-        groovyClass.annotationTypeNames == []
-
-        def groovyInterface = repository.get('org.gradle.test.GroovyInterface')
-        groovyInterface.groovy
-        groovyInterface.isInterface()
-        groovyInterface.superClassName == null
-        groovyInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
-        groovyInterface.annotationTypeNames == []
-    }
-
-    def extractsClassMetaDataFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClass.java')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-        task.source testFile('org/gradle/test/A.groovy')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/Interface1.java')
-        task.source testFile('org/gradle/test/Interface2.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClass')
-        !javaClass.groovy
-        !javaClass.isInterface()
-        javaClass.rawCommentText.contains('This is a java class.')
-        javaClass.superClassName == 'org.gradle.test.A'
-        javaClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
-        javaClass.annotationTypeNames == []
-
-        def javaInterface = repository.get('org.gradle.test.JavaInterface')
-        !javaInterface.groovy
-        javaInterface.isInterface()
-        javaInterface.superClassName == null
-        javaInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
-        javaInterface.annotationTypeNames == []
-    }
-
-    def extractsPropertyMetaDataFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClass.groovy')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClass')
-        groovyClass.declaredPropertyNames == ['readOnly', 'writeOnly', 'someProp', 'groovyProp', 'readOnlyGroovyProp', 'arrayProp'] as Set
-
-        def readOnly = groovyClass.findDeclaredProperty('readOnly')
-        readOnly.type.signature == 'java.lang.Object'
-        readOnly.rawCommentText.contains('A read-only property.')
-        !readOnly.writeable
-        readOnly.getter.rawCommentText.contains('A read-only property.')
-        !readOnly.setter
-
-        def writeOnly = groovyClass.findDeclaredProperty('writeOnly')
-        writeOnly.type.signature == 'org.gradle.test.JavaInterface'
-        writeOnly.rawCommentText.contains('A write-only property.')
-        writeOnly.writeable
-        !writeOnly.getter
-        writeOnly.setter.rawCommentText.contains('A write-only property.')
-
-        def someProp = groovyClass.findDeclaredProperty('someProp')
-        someProp.type.signature == 'org.gradle.test.GroovyInterface'
-        someProp.rawCommentText.contains('A property.')
-        someProp.writeable
-        someProp.getter.rawCommentText.contains('A property.')
-        someProp.setter.rawCommentText == ''
-
-        def groovyProp = groovyClass.findDeclaredProperty('groovyProp')
-        groovyProp.type.signature == 'org.gradle.test.GroovyInterface'
-        groovyProp.rawCommentText.contains('A groovy property.')
-        groovyProp.writeable
-        groovyProp.getter.rawCommentText == ''
-        groovyProp.setter.rawCommentText == ''
-
-        def readOnlyGroovyProp = groovyClass.findDeclaredProperty('readOnlyGroovyProp')
-        readOnlyGroovyProp.type.signature == 'java.lang.String'
-        readOnlyGroovyProp.rawCommentText.contains('A read-only groovy property.')
-        !readOnlyGroovyProp.writeable
-        readOnlyGroovyProp.getter.rawCommentText == ''
-        !readOnlyGroovyProp.setter
-
-        def arrayProp = groovyClass.findDeclaredProperty('arrayProp')
-        arrayProp.type.signature == 'java.lang.String[]'
-        arrayProp.rawCommentText.contains('An array property.')
-        arrayProp.writeable
-        arrayProp.getter.rawCommentText == ''
-        arrayProp.setter.rawCommentText == ''
-    }
-
-    def extractsPropertyMetaDataFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClass.java')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClass')
-        javaClass.declaredPropertyNames == ['readOnly', 'writeOnly', 'someProp', 'flag', 'arrayProp'] as Set
-
-        def readOnly = javaClass.findDeclaredProperty('readOnly')
-        readOnly.type.signature == 'java.lang.String'
-        readOnly.rawCommentText.contains('A read-only property.')
-        !readOnly.writeable
-        readOnly.getter.rawCommentText.contains('A read-only property.')
-        !readOnly.setter
-
-        def writeOnly = javaClass.findDeclaredProperty('writeOnly')
-        writeOnly.type.signature == 'org.gradle.test.JavaInterface'
-        writeOnly.rawCommentText.contains('A write-only property.')
-        writeOnly.writeable
-        !writeOnly.getter
-        writeOnly.setter.rawCommentText.contains('A write-only property.')
-
-        def someProp = javaClass.findDeclaredProperty('someProp')
-        someProp.type.signature == 'org.gradle.test.JavaInterface'
-        someProp.rawCommentText.contains('A property.')
-        someProp.writeable
-        someProp.getter.rawCommentText.contains('A property.')
-        someProp.setter.rawCommentText.contains('The setter for a property.')
-
-        def flag = javaClass.findDeclaredProperty('flag')
-        flag.type.signature == 'boolean'
-        flag.rawCommentText.contains('A boolean property.')
-        !flag.writeable
-        flag.getter.rawCommentText.contains('A boolean property.')
-        !flag.setter
-
-        def arrayProp = javaClass.findDeclaredProperty('arrayProp')
-        arrayProp.type.signature == 'org.gradle.test.JavaInterface[][][]'
-        arrayProp.rawCommentText.contains('An array property.')
-        !arrayProp.writeable
-        arrayProp.getter.rawCommentText.contains('An array property.')
-        !arrayProp.setter
-    }
-
-    def extractsMethodMetaDataFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithMethods.groovy')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithMethods')
-        groovyClass.declaredMethods.collect { it.name } as Set == ['stringMethod', 'refTypeMethod', 'defMethod', 'voidMethod', 'arrayMethod', 'setProp', 'getProp', 'getFinalProp', 'getIntProp', 'setIntProp'] as Set
-
-        def stringMethod = groovyClass.declaredMethods.find { it.name == 'stringMethod' }
-        stringMethod.rawCommentText.contains('A method that returns String')
-        stringMethod.returnType.signature == 'java.lang.String'
-        stringMethod.parameters.collect { it.name } == ['stringParam']
-        stringMethod.parameters[0].name == 'stringParam'
-        stringMethod.parameters[0].type.signature == 'java.lang.String'
-
-        def refTypeMethod = groovyClass.declaredMethods.find { it.name == 'refTypeMethod' }
-        refTypeMethod.rawCommentText.contains('A method that returns a reference type.')
-        refTypeMethod.returnType.signature == 'org.gradle.test.GroovyInterface'
-        refTypeMethod.parameters.collect { it.name } == ['someThing', 'aFlag']
-        refTypeMethod.parameters[0].name == 'someThing'
-        refTypeMethod.parameters[0].type.signature == 'org.gradle.test.JavaInterface'
-        refTypeMethod.parameters[1].name == 'aFlag'
-        refTypeMethod.parameters[1].type.signature == 'boolean'
-
-        def defMethod = groovyClass.declaredMethods.find { it.name == 'defMethod' }
-        defMethod.rawCommentText.contains('A method that returns a default type.')
-        defMethod.returnType.signature == 'java.lang.Object'
-        defMethod.parameters.collect { it.name } == ['defParam']
-        defMethod.parameters[0].name == 'defParam'
-        defMethod.parameters[0].type.signature == 'java.lang.Object'
-
-        def voidMethod = groovyClass.declaredMethods.find { it.name == 'voidMethod' }
-        voidMethod.rawCommentText.contains('A method that returns void.')
-        voidMethod.returnType.signature == 'void'
-        voidMethod.parameters.collect { it.name } == []
-
-        def arrayMethod = groovyClass.declaredMethods.find { it.name == 'arrayMethod' }
-        arrayMethod.returnType.signature == 'java.lang.String[][]'
-        arrayMethod.returnType.arrayDimensions == 2
-        arrayMethod.parameters.collect { it.name } == ['strings']
-        arrayMethod.parameters[0].name == 'strings'
-        arrayMethod.parameters[0].type.signature == 'java.lang.String[]...'
-        arrayMethod.parameters[0].type.arrayDimensions == 2
-
-        def getProp = groovyClass.declaredMethods.find { it.name == 'getProp' }
-        getProp.rawCommentText == ''
-        getProp.returnType.signature == 'java.lang.String'
-        getProp.parameters.collect { it.name } == []
-
-        def setProp = groovyClass.declaredMethods.find { it.name == 'setProp' }
-        setProp.rawCommentText == ''
-        setProp.returnType.signature == 'void'
-        setProp.parameters.collect { it.name } == ['prop']
-        setProp.parameters[0].name == 'prop'
-        setProp.parameters[0].type.signature == 'java.lang.String'
-
-        def getFinalProp = groovyClass.declaredMethods.find { it.name == 'getFinalProp' }
-        getFinalProp.rawCommentText == ''
-        getFinalProp.returnType.signature == 'org.gradle.test.JavaInterface'
-        getFinalProp.parameters.collect { it.name } == []
-
-        groovyClass.declaredPropertyNames == ['prop', 'finalProp', 'intProp'] as Set
-    }
-
-    def extractsMethodMetaDataFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithMethods.java')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithMethods')
-        javaClass.declaredMethods.collect { it.name } as Set == ['stringMethod', 'refTypeMethod', 'voidMethod', 'arrayMethod', 'getIntProp', 'setIntProp'] as Set
-
-        def stringMethod = javaClass.declaredMethods.find { it.name == 'stringMethod' }
-        stringMethod.rawCommentText.contains('A method that returns String')
-        stringMethod.returnType.signature == 'java.lang.String'
-        stringMethod.parameters.collect { it.name } == ['stringParam']
-        stringMethod.parameters[0].name == 'stringParam'
-        stringMethod.parameters[0].type.signature == 'java.lang.String'
-
-        def refTypeMethod = javaClass.declaredMethods.find { it.name == 'refTypeMethod' }
-        refTypeMethod.rawCommentText.contains('A method that returns a reference type.')
-        refTypeMethod.returnType.signature == 'org.gradle.test.GroovyInterface'
-        refTypeMethod.parameters.collect { it.name } == ['refParam', 'aFlag']
-        refTypeMethod.parameters[0].name == 'refParam'
-        refTypeMethod.parameters[0].type.signature == 'org.gradle.test.JavaInterface'
-        refTypeMethod.parameters[1].name == 'aFlag'
-        refTypeMethod.parameters[1].type.signature == 'boolean'
-
-        def voidMethod = javaClass.declaredMethods.find { it.name == 'voidMethod' }
-        voidMethod.rawCommentText.contains('A method that returns void.')
-        voidMethod.returnType.signature == 'void'
-        voidMethod.parameters.collect { it.name } == []
-
-        def arrayMethod = javaClass.declaredMethods.find { it.name == 'arrayMethod' }
-        arrayMethod.returnType.signature == 'java.lang.String[][]'
-        arrayMethod.returnType.arrayDimensions == 2
-        arrayMethod.parameters.collect { it.name } == ['strings']
-        arrayMethod.parameters[0].name == 'strings'
-        arrayMethod.parameters[0].type.signature == 'java.lang.String[]...'
-        arrayMethod.parameters[0].type.arrayDimensions == 2
-
-        javaClass.declaredPropertyNames == ['intProp'] as Set
-    }
-
-    def extractsConstantsFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithConstants.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithConstants')
-        groovyClass.constants.keySet() == ['INT_CONST', 'STRING_CONST', 'OBJECT_CONST', 'BIG_DECIMAL_CONST'] as Set
-
-        groovyClass.constants['INT_CONST'] == '9'
-        groovyClass.constants['STRING_CONST'] == 'some-string'
-        groovyClass.constants['BIG_DECIMAL_CONST'] == '1.02'
-        groovyClass.constants['OBJECT_CONST'] == null
-    }
-
-    def extractsConstantsFromJavaClassSource() {
-        task.source testFile('org/gradle/test/JavaClassWithConstants.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithConstants')
-        javaClass.constants.keySet() == ['INT_CONST', 'STRING_CONST', 'OBJECT_CONST', 'CHAR_CONST'] as Set
-
-        javaClass.constants['INT_CONST'] == '9'
-        javaClass.constants['STRING_CONST'] == 'some-string'
-        javaClass.constants['CHAR_CONST'] == 'a'
-        javaClass.constants['OBJECT_CONST'] == null
-    }
-
-    def extractsConstantsFromJavaInterfaceSource() {
-        task.source testFile('org/gradle/test/JavaInterfaceWithConstants.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaInterface = repository.get('org.gradle.test.JavaInterfaceWithConstants')
-        javaInterface.constants.keySet() == ['INT_CONST', 'STRING_CONST'] as Set
-
-        javaInterface.constants['INT_CONST'] == '120'
-        javaInterface.constants['STRING_CONST'] == 'some-string'
-    }
-
-    def handlesFullyQualifiedNamesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithFullyQualifiedNames.groovy')
-        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
-        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithFullyQualifiedNames')
-        groovyClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
-        groovyClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'java.lang.Runnable']
-        groovyClass.declaredPropertyNames == ['prop'] as Set
-
-        def prop = groovyClass.findDeclaredProperty('prop')
-        prop.type.signature == 'org.gradle.test.sub.SubJavaInterface'
-    }
-
-    def handlesFullyQualifiedNamesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithFullyQualifiedNames.java')
-        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
-        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithFullyQualifiedNames')
-        javaClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
-        javaClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'java.lang.Runnable']
-        javaClass.declaredPropertyNames == ['prop'] as Set
-
-        def prop = javaClass.findDeclaredProperty('prop')
-        prop.type.signature == 'org.gradle.test.sub.SubJavaInterface'
-    }
-
-    def handlesImportedTypesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithImports.groovy')
-        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
-        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
-        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithImports')
-        groovyClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
-        groovyClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'org.gradle.test.sub2.GroovyInterface']
-        groovyClass.declaredPropertyNames == [] as Set
-    }
-
-    def handlesImportedTypesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithImports.java')
-        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
-        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
-        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithImports')
-        javaClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
-        javaClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'org.gradle.test.sub2.GroovyInterface', 'java.io.Closeable']
-        javaClass.declaredPropertyNames == [] as Set
-    }
-
-    def handlesEnumTypesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyEnum.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyEnum = repository.get('org.gradle.test.GroovyEnum')
-        groovyEnum.groovy
-        !groovyEnum.isInterface()
-    }
-
-    def handlesEnumTypesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaEnum.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaEnum = repository.get('org.gradle.test.JavaEnum')
-        !javaEnum.groovy
-        !javaEnum.isInterface()
-    }
-
-    def handlesAnnotationTypesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyAnnotation.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def annotation = repository.get('org.gradle.test.GroovyAnnotation')
-        annotation.groovy
-        !annotation.isInterface()
-    }
-
-    def handlesAnnotationTypesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def annotation = repository.get('org.gradle.test.JavaAnnotation')
-        !annotation.groovy
-        !annotation.isInterface()
-    }
-
-    def handlesNestedAndAnonymousTypesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithInnerTypes.groovy')
-        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithInnerTypes')
-        groovyClass.interfaceNames == ['org.gradle.test.sub2.GroovyInterface']
-        groovyClass.declaredPropertyNames == ['someProp', 'innerClassProp'] as Set
-
-        def someProp = groovyClass.findDeclaredProperty('someProp')
-        someProp.type.signature == 'org.gradle.test.sub2.GroovyInterface'
-
-        def innerClassProp = groovyClass.findDeclaredProperty('innerClassProp')
-        innerClassProp.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerClass.AnotherInner'
-
-        def innerEnum = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerEnum')
-        innerEnum.rawCommentText.contains('This is an inner enum.')
-
-        def innerClass = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerClass')
-        innerClass.rawCommentText.contains('This is an inner class.')
-        innerClass.declaredPropertyNames == ['enumProp'] as Set
-
-        def enumProp = innerClass.findDeclaredProperty('enumProp')
-        enumProp.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerEnum'
-
-        def anotherInner = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerClass.AnotherInner')
-        anotherInner.rawCommentText.contains('This is an inner inner class.')
-        anotherInner.declaredPropertyNames == ['outer'] as Set
-
-        def outer = anotherInner.findDeclaredProperty('outer')
-        outer.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerClass'
-    }
-
-    def handlesNestedAndAnonymousTypesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithInnerTypes.java')
-        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithInnerTypes')
-        javaClass.interfaceNames == ['org.gradle.test.sub2.GroovyInterface']
-        javaClass.declaredPropertyNames == ['someProp', 'innerClassProp'] as Set
-
-        def someProp = javaClass.findDeclaredProperty('someProp')
-        someProp.type.signature == 'org.gradle.test.sub2.GroovyInterface'
-
-        def innerClassProp = javaClass.findDeclaredProperty('innerClassProp')
-        innerClassProp.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerClass.AnotherInner'
-
-        def innerEnum = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerEnum')
-        innerEnum.rawCommentText.contains('This is an inner enum.')
-
-        def innerClass = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerClass')
-        innerClass.rawCommentText.contains('This is an inner class.')
-        innerClass.declaredPropertyNames == ['enumProp'] as Set
-
-        def enumProp = innerClass.findDeclaredProperty('enumProp')
-        enumProp.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerEnum'
-
-        def anotherInner = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerClass.AnotherInner')
-        anotherInner.rawCommentText.contains('This is an inner inner class.')
-        anotherInner.declaredPropertyNames == ['outer'] as Set
-
-        def outer = anotherInner.findDeclaredProperty('outer')
-        outer.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerClass'
-    }
-
-    def handlesParameterizedTypesInGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithParameterizedTypes.groovy')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithParameterizedTypes')
-
-        def setProp = groovyClass.findDeclaredProperty('setProp')
-        setProp.type.signature == 'java.util.Set<org.gradle.test.GroovyInterface>'
-
-        def mapProp = groovyClass.findDeclaredProperty('mapProp')
-        mapProp.type.signature == 'java.util.Map<org.gradle.test.GroovyInterface, org.gradle.test.GroovyClassWithParameterizedTypes>'
-
-        def wilcardProp = groovyClass.findDeclaredProperty('wildcardProp')
-        wilcardProp.type.signature == 'java.util.List<?>'
-
-        def upperBoundProp = groovyClass.findDeclaredProperty('upperBoundProp')
-        upperBoundProp.type.signature == 'java.util.List<? extends org.gradle.test.GroovyInterface>'
-
-        def lowerBoundProp = groovyClass.findDeclaredProperty('lowerBoundProp')
-        lowerBoundProp.type.signature == 'java.util.List<? super org.gradle.test.GroovyInterface>'
-
-        def nestedProp = groovyClass.findDeclaredProperty('nestedProp')
-        nestedProp.type.signature == 'java.util.List<? super java.util.Set<? extends java.util.Map<?, org.gradle.test.GroovyInterface[]>>>[]'
-
-        def paramMethod = groovyClass.declaredMethods.find { it.name == 'paramMethod' }
-        paramMethod.returnType.signature == 'T'
-        paramMethod.parameters[0].type.signature == 'T'
-    }
-
-    def handlesParameterizedTypesInJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithParameterizedTypes.java')
-        task.source testFile('org/gradle/test/GroovyInterface.groovy')
-        task.source testFile('org/gradle/test/JavaInterface.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithParameterizedTypes')
-
-        def setProp = javaClass.findDeclaredProperty('setProp')
-        setProp.type.signature == 'java.util.Set<org.gradle.test.GroovyInterface>'
-
-        def mapProp = javaClass.findDeclaredProperty('mapProp')
-        mapProp.type.signature == 'java.util.Map<org.gradle.test.GroovyInterface, org.gradle.test.JavaClassWithParameterizedTypes>'
-
-        def wildcardProp = javaClass.findDeclaredProperty('wildcardProp')
-        wildcardProp.type.signature == 'java.util.List<?>'
-
-        def upperBoundProp = javaClass.findDeclaredProperty('upperBoundProp')
-        upperBoundProp.type.signature == 'java.util.List<? extends org.gradle.test.GroovyInterface>'
-
-        def lowerBoundProp = javaClass.findDeclaredProperty('lowerBoundProp')
-        lowerBoundProp.type.signature == 'java.util.List<? super org.gradle.test.GroovyInterface>'
-
-        def nestedProp = javaClass.findDeclaredProperty('nestedProp')
-        nestedProp.type.signature == 'java.util.List<? super java.util.Set<? extends java.util.Map<?, org.gradle.test.GroovyInterface[]>>>[]'
-
-        def paramMethod = javaClass.declaredMethods.find { it.name == 'paramMethod' }
-        paramMethod.returnType.signature == 'T'
-        paramMethod.parameters[0].type.signature == 'T'
-    }
-
-    def extractsClassAnnotationsFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
-        groovyClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def extractsClassAnnotationsFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
-        task.source testFile('org/gradle/test/JavaInterfaceWithAnnotation.java')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
-        javaClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-
-        def javaInterface = repository.get('org.gradle.test.JavaInterfaceWithAnnotation')
-        javaInterface.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def extractsMethodAnnotationsFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
-        def method = groovyClass.declaredMethods.find { it.name == 'annotatedMethod' }
-        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def extractsMethodAnnotationsFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
-        def method = javaClass.declaredMethods.find { it.name == 'annotatedMethod' }
-        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def extractsPropertyAnnotationsFromGroovySource() {
-        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
-        def property = groovyClass.declaredProperties.find { it.name == 'annotatedProperty' }
-        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def extractsPropertyAnnotationsFromJavaSource() {
-        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
-        task.source testFile('org/gradle/test/JavaAnnotation.java')
-
-        when:
-        task.extract()
-        repository.load(task.destFile)
-
-        then:
-        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
-        def property = javaClass.declaredProperties.find { it.name == 'annotatedProperty' }
-        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
-    }
-
-    def testFile(String fileName) {
-        URL resource = getClass().classLoader.getResource(fileName)
-        assert resource != null: "Could not find resource '$fileName'."
-        assert resource.protocol == 'file'
-        return new File(resource.toURI())
-    }
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/TypeNameResolverTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/TypeNameResolverTest.groovy
deleted file mode 100644
index caf1bdf..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/TypeNameResolverTest.groovy
+++ /dev/null
@@ -1,178 +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
-
-import org.gradle.build.docs.dsl.model.ClassMetaData
-import org.gradle.build.docs.model.ClassMetaDataRepository
-import spock.lang.Specification
-import org.gradle.build.docs.dsl.model.TypeMetaData
-
-class TypeNameResolverTest extends Specification {
-    final ClassMetaDataRepository<ClassMetaData> metaDataRepository = Mock()
-    final ClassMetaData classMetaData = Mock()
-    final TypeNameResolver typeNameResolver = new TypeNameResolver(metaDataRepository)
-
-    def resolvesFullyQualifiedClassName() {
-        when:
-        def name = typeNameResolver.resolve('org.gradle.SomeClass', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass'
-        _ * classMetaData.innerClassNames >> []
-    }
-
-    def resolvesUnqualifiedNameToClassInSamePackage() {
-        when:
-        def name = typeNameResolver.resolve('SomeClass', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> []
-        _ * classMetaData.packageName >> 'org.gradle'
-        _ * metaDataRepository.find('org.gradle.SomeClass') >> classMetaData
-    }
-
-    def resolvesUnqualifiedNameToImportedClass() {
-        when:
-        def name = typeNameResolver.resolve('SomeClass', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> ['org.gradle.SomeClass']
-    }
-
-    def resolvesUnqualifiedNameToImportedPackage() {
-        when:
-        def name = typeNameResolver.resolve('SomeClass', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> ['org.gradle.*']
-        _ * metaDataRepository.find('org.gradle.SomeClass') >> classMetaData
-    }
-
-    def resolvesUnqualifiedNameToInnerClass() {
-        when:
-        def name = typeNameResolver.resolve('Inner', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass.Inner'
-        _ * classMetaData.innerClassNames >> ['org.gradle.SomeClass.Inner']
-        _ * classMetaData.className >> 'org.gradle.SomeClass'
-    }
-
-    def resolvesQualifiedNameToInnerClass() {
-        ClassMetaData innerClass = Mock()
-
-        when:
-        def name = typeNameResolver.resolve('A.B', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass.A.B'
-        _ * classMetaData.innerClassNames >> ['org.gradle.SomeClass.A']
-        _ * classMetaData.className >> 'org.gradle.SomeClass'
-        _ * metaDataRepository.get('org.gradle.SomeClass.A') >> innerClass
-        _ * innerClass.innerClassNames >> ['org.gradle.SomeClass.A.B']
-        _ * innerClass.className >> 'org.gradle.SomeClass.A'
-    }
-
-    def resolvesUnqualifiedNameToOuterClass() {
-        when:
-        def name = typeNameResolver.resolve('Outer', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass.Outer'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.outerClassName >> 'org.gradle.SomeClass.Outer'
-    }
-
-    def resolvesUnqualifiedNameToSiblingClass() {
-        ClassMetaData outerClass = Mock()
-
-        when:
-        def name = typeNameResolver.resolve('Sibling', classMetaData)
-
-        then:
-        name == 'org.gradle.SomeClass.Outer.Sibling'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.outerClassName >> 'org.gradle.SomeClass.Outer'
-        _ * metaDataRepository.get('org.gradle.SomeClass.Outer') >> outerClass
-        _ * outerClass.innerClassNames >> ['org.gradle.SomeClass.Outer.Sibling']
-    }
-
-    def resolvesUnqualifiedNameToJavaLangPackage() {
-        when:
-        def name = typeNameResolver.resolve('String', classMetaData)
-
-        then:
-        name == 'java.lang.String'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> []
-    }
-
-    def resolvesUnqualifiedNameToDefaultPackagesAndClassesInGroovySource() {
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> []
-        _ * classMetaData.groovy >> true
-
-        expect:
-        typeNameResolver.resolve('Set', classMetaData) == 'java.util.Set'
-        typeNameResolver.resolve('File', classMetaData) == 'java.io.File'
-        typeNameResolver.resolve('Closure', classMetaData) == 'groovy.lang.Closure'
-        typeNameResolver.resolve('BigDecimal', classMetaData) == 'java.math.BigDecimal'
-        typeNameResolver.resolve('BigInteger', classMetaData) == 'java.math.BigInteger'
-    }
-
-    def resolvesUnqualifiedNameToImportedJavaPackage() {
-        when:
-        def name = typeNameResolver.resolve('Set', classMetaData)
-
-        then:
-        name == 'java.util.Set'
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> ['java.util.*']
-    }
-
-    def resolvesPrimitiveType() {
-        when:
-        def name = typeNameResolver.resolve('boolean', classMetaData)
-
-        then:
-        name == 'boolean'
-    }
-
-    def resolvesParameterisedTypes() {
-        def typeMetaData = type('SomeClass')
-        typeMetaData.addTypeArg(type('String'))
-
-        when:
-        typeNameResolver.resolve(typeMetaData, classMetaData)
-
-        then:
-        typeMetaData.signature == 'org.gradle.SomeClass<java.lang.String>'
-
-        _ * classMetaData.innerClassNames >> []
-        _ * classMetaData.imports >> ['org.gradle.SomeClass']
-    }
-
-    def type(String name) {
-        return new TypeMetaData(name)
-    }
-}
-
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexerTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexerTest.groovy
index 9437165..dd9df8b 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexerTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/BasicJavadocLexerTest.groovy
@@ -31,6 +31,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartHtmlElementComplete('p')
         1 * visitor.onText(' text ')
         1 * visitor.onEndHtmlElement('p')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -43,6 +44,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartHtmlElement('p')
         1 * visitor.onStartHtmlElementComplete('p')
         1 * visitor.onEndHtmlElement('end')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -53,6 +55,7 @@ class BasicJavadocLexerTest extends Specification {
 
         then:
         1 * visitor.onText('before & after')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -66,6 +69,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onHtmlElementAttribute('name', 'value')
         1 * visitor.onHtmlElementAttribute('other', '\n& \'\n \"')
         1 * visitor.onStartHtmlElementComplete('a')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -79,6 +83,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onHtmlElementAttribute('single', 'a=\"b\"')
         1 * visitor.onHtmlElementAttribute('double', 'a=\'b\'')
         1 * visitor.onStartHtmlElementComplete('a')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -91,6 +96,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartHtmlElement('p')
         1 * visitor.onStartHtmlElementComplete('p')
         1 * visitor.onEndHtmlElement('p')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -103,6 +109,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartJavadocTag('tag')
         1 * visitor.onText('some value')
         1 * visitor.onEndJavadocTag('tag')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -114,6 +121,7 @@ class BasicJavadocLexerTest extends Specification {
         then:
         1 * visitor.onStartJavadocTag('empty')
         1 * visitor.onEndJavadocTag('empty')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -126,6 +134,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartJavadocTag('link')
         1 * visitor.onText('Something')
         1 * visitor.onEndJavadocTag('link')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -138,6 +147,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartJavadocTag('link')
         1 * visitor.onText('#Something(Object,\nString\n')
         1 * visitor.onEndJavadocTag('link')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -150,6 +160,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onStartJavadocTag('link')
         1 * visitor.onText('<something> & < </something>')
         1 * visitor.onEndJavadocTag('link')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 
@@ -164,6 +175,7 @@ class BasicJavadocLexerTest extends Specification {
         1 * visitor.onText('code')
         1 * visitor.onEndJavadocTag('')
         1 * visitor.onText(' { @ code}')
+        1 * visitor.onEnd()
         0 * visitor._
     }
 }
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilderTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilderTest.groovy
new file mode 100644
index 0000000..9861f71
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocExtensionsBuilderTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.XmlSpecification
+
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
+import org.gradle.build.docs.dsl.source.model.ParameterMetaData
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionMetaData
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
+
+class ClassDocExtensionsBuilderTest extends XmlSpecification {
+    final DslDocModel docModel = Mock()
+    final ClassDocExtensionsBuilder builder = new ClassDocExtensionsBuilder(docModel, null)
+
+    def buildsExtensionsForClassMixins() {
+        ClassMetaData classMetaData = classMetaData()
+        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')
+        _ * docModel.getClassDoc('org.gradle.ExtensionA1') >> extensionA1
+        _ * docModel.getClassDoc('org.gradle.ExtensionA2') >> extensionA2
+        _ * docModel.getClassDoc('org.gradle.ExtensionB') >> extensionB
+
+        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 {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, extensionMetaData)
+            builder.build(doc)
+            doc.mergeContent()
+        }
+
+        then:
+        doc.classExtensions.size() == 2
+
+        doc.classExtensions[0].pluginId == 'a'
+        doc.classExtensions[0].mixinClasses == [extensionA1, extensionA2] as Set
+
+        doc.classExtensions[1].pluginId == 'b'
+        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 {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, extensionMetaData)
+            builder.build(doc)
+            doc.mergeContent()
+        }
+
+        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') {
+        ClassMetaData classMetaData = Mock()
+        _ * classMetaData.className >> name
+        return classMetaData
+    }
+
+    def classDoc(String name = 'org.gradle.Class') {
+        ClassDoc doc = Mock()
+        _ * doc.name >> name
+        _ * doc.toString() >> "ClassDoc '$name'"
+        return doc
+    }
+
+    def property(String name, ClassMetaData classMetaData) {
+        return property([:], name, classMetaData)
+    }
+
+    def property(Map<String, ?> args, String name, ClassMetaData classMetaData) {
+        PropertyMetaData property = Mock()
+        _ * property.name >> name
+        _ * property.ownerClass >> classMetaData
+        def type = args.type instanceof TypeMetaData ? args.type : new TypeMetaData(args.type ?: 'org.gradle.Type')
+        _ * property.type >> type
+        _ * property.signature >> "$name-signature"
+        _ * javadocConverter.parse(property, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
+        return property
+    }
+
+    def propertyDoc(Map<String, ?> args = [:], String name) {
+        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues ?: [])
+    }
+
+    def method(String name, ClassMetaData classMetaData) {
+        return method([:], name, classMetaData)
+    }
+
+    def method(Map<String, ?> args, String name, ClassMetaData classMetaData) {
+        MethodMetaData method = Mock()
+        List<String> paramTypes = args.paramTypes ?: []
+        _ * method.name >> name
+        _ * method.overrideSignature >> "$name(${paramTypes.join(', ')})"
+        _ * method.parameters >> paramTypes.collect {
+            def param = new ParameterMetaData("p");
+            param.type = new TypeMetaData(it)
+            return param
+        }
+        _ * method.ownerClass >> classMetaData
+        _ * method.returnType >> new TypeMetaData(args.returnType ?: 'ReturnType')
+        _ * javadocConverter.parse(method, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
+        return method
+    }
+
+    def methodDoc(String name) {
+        MethodDoc methodDoc = Mock()
+        _ * methodDoc.name >> name
+        _ * methodDoc.metaData >> method(name, null)
+        _ * methodDoc.forClass(!null) >> methodDoc
+        return methodDoc
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilderTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilderTest.groovy
new file mode 100644
index 0000000..a514f97
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocMethodsBuilderTest.groovy
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.XmlSpecification
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc
+import org.gradle.build.docs.dsl.source.model.*
+
+class ClassDocMethodsBuilderTest extends XmlSpecification {
+    final JavadocConverter javadocConverter = Mock()
+    final ClassDocMethodsBuilder builder = new ClassDocMethodsBuilder(javadocConverter, null)
+
+    def buildsMethodsForClass() {
+        ClassMetaData classMetaData = classMetaData()
+        MethodMetaData methodA = method('a', classMetaData)
+        MethodMetaData methodB = method('b', classMetaData)
+        MethodMetaData methodBOverload = method('b', classMetaData)
+        MethodDoc methodAOverridden = methodDoc('a')
+        MethodDoc methodC = methodDoc('c')
+        ClassDoc superClass = classDoc('org.gradle.SuperClass')
+
+        def content = parse('''
+<section>
+    <section><title>Methods</title>
+        <table>
+            <thead><tr><td>Name</td></tr></thead>
+            <tr><td>a</td></tr>
+            <tr><td>b</td></tr>
+        </table>
+    </section>
+    <section><title>Properties</title><table><thead><tr>Name</tr></thead></table></section>
+</section>
+''')
+
+        when:
+        ClassDoc doc = withCategories {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
+            doc.superClass = superClass
+            builder.build(doc)
+            doc
+        }
+
+        then:
+        doc.classMethods.size() == 4
+
+        doc.classMethods[0].name == 'a'
+        doc.classMethods[1].name == 'b'
+        doc.classMethods[2].name == 'b'
+        doc.classMethods[3].name == 'c'
+
+        _ * classMetaData.declaredMethods >> ([methodA, methodB, methodBOverload] as Set)
+        _ * classMetaData.findDeclaredMethods("a") >> [methodA]
+        _ * classMetaData.findDeclaredMethods("b") >> [methodB, methodBOverload]
+        _ * classMetaData.superClassName >> 'org.gradle.SuperClass'
+        _ * superClass.classMethods >> [methodC, methodAOverridden]
+    }
+
+    def buildsBlocksForClass() {
+        ClassMetaData classMetaData = classMetaData()
+        PropertyMetaData blockProperty = property('block', classMetaData)
+        MethodMetaData blockMethod = method('block', classMetaData, paramTypes: [Closure.class.name])
+        PropertyMetaData compositeBlockProperty = property('listBlock', classMetaData, type: new TypeMetaData('java.util.List').addTypeArg(new TypeMetaData('BlockType')))
+        MethodMetaData compositeBlockMethod = method('listBlock', classMetaData, paramTypes: [Closure.class.name])
+        MethodMetaData tooManyParams = method('block', classMetaData, paramTypes: ['String', 'boolean'])
+        MethodMetaData notAClosure = method('block', classMetaData, paramTypes: ['String'])
+        MethodMetaData noBlockProperty = method('notBlock', classMetaData, paramTypes: [Closure.class.name])
+        _ * classMetaData.findProperty('block') >> blockProperty
+        _ * classMetaData.findProperty('listBlock') >> compositeBlockProperty
+        _ * classMetaData.declaredMethods >> [blockMethod, compositeBlockMethod, tooManyParams, notAClosure, noBlockProperty]
+        _ * classMetaData.findDeclaredMethods('listBlock') >> [compositeBlockMethod]
+        _ * classMetaData.findDeclaredMethods('block') >> [tooManyParams, notAClosure, blockMethod]
+        _ * classMetaData.findDeclaredMethods('notBlock') >> [noBlockProperty]
+
+        def content = parse('''
+<section>
+    <section><title>Methods</title>
+        <table>
+            <thead><tr><td>Name</td></tr></thead>
+            <tr><td>block</td></tr>
+            <tr><td>listBlock</td></tr>
+            <tr><td>notBlock</td></tr>
+        </table>
+    </section>
+    <section><title>Properties</title>
+        <table>
+            <thead><tr><td>Name</td></tr></thead>
+            <tr><td>block</td></tr>
+            <tr><td>listBlock</td></tr>
+        </table>
+    </section>
+</section>
+''')
+
+        when:
+        ClassDoc doc = withCategories {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
+            new ClassDocPropertiesBuilder(javadocConverter, Mock(GenerationListener)).build(doc)
+            new ClassDocMethodsBuilder(javadocConverter, Mock(GenerationListener)).build(doc)
+            doc
+        }
+
+        then:
+        doc.classProperties.size() == 2
+        doc.classProperties[0].name == 'block'
+        doc.classProperties[1].name == 'listBlock'
+
+        doc.classMethods.size() == 3
+
+        doc.classBlocks.size() == 2
+        doc.classBlocks[0].name == 'block'
+        doc.classBlocks[0].type.signature == 'org.gradle.Type'
+        !doc.classBlocks[0].multiValued
+
+        doc.classBlocks[1].name == 'listBlock'
+        doc.classBlocks[1].type.signature == 'BlockType'
+        doc.classBlocks[1].multiValued
+    }
+
+    def classMetaData(String name = 'org.gradle.Class') {
+        ClassMetaData classMetaData = Mock()
+        _ * classMetaData.className >> name
+        return classMetaData
+    }
+
+    def classDoc(String name = 'org.gradle.Class') {
+        ClassDoc doc = Mock()
+        _ * doc.name >> name
+        _ * doc.toString() >> "ClassDoc '$name'"
+        return doc
+    }
+
+    def property(String name, ClassMetaData classMetaData) {
+        return property([:], name, classMetaData)
+    }
+
+    def property(Map<String, ?> args, String name, ClassMetaData classMetaData) {
+        PropertyMetaData property = Mock()
+        _ * property.name >> name
+        _ * property.ownerClass >> classMetaData
+        def type = args.type instanceof TypeMetaData ? args.type : new TypeMetaData(args.type ?: 'org.gradle.Type')
+        _ * property.type >> type
+        _ * property.signature >> "$name-signature"
+        _ * javadocConverter.parse(property, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
+        return property
+    }
+
+    def propertyDoc(Map<String, ?> args = [:], String name) {
+        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues ?: [])
+    }
+
+    def method(String name, ClassMetaData classMetaData) {
+        return method([:], name, classMetaData)
+    }
+
+    def method(Map<String, ?> args, String name, ClassMetaData classMetaData) {
+        MethodMetaData method = Mock()
+        List<String> paramTypes = args.paramTypes ?: []
+        _ * method.name >> name
+        _ * method.overrideSignature >> "$name(${paramTypes.join(', ')})"
+        _ * method.parameters >> paramTypes.collect {
+            def param = new ParameterMetaData("p");
+            param.type = new TypeMetaData(it)
+            return param
+        }
+        _ * method.ownerClass >> classMetaData
+        _ * method.returnType >> new TypeMetaData(args.returnType ?: 'ReturnType')
+        _ * javadocConverter.parse(method, _) >> ({[parse("<para>comment</para>")]} as DocComment)
+        return method
+    }
+
+    def methodDoc(String name) {
+        MethodDoc methodDoc = Mock()
+        _ * methodDoc.name >> name
+        _ * methodDoc.metaData >> method(name, null)
+        _ * methodDoc.forClass(!null) >> methodDoc
+        return methodDoc
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilderTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilderTest.groovy
new file mode 100644
index 0000000..22570d2
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocPropertiesBuilderTest.groovy
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.XmlSpecification
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
+import org.gradle.build.docs.dsl.docbook.model.ExtraAttributeDoc
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+
+class ClassDocPropertiesBuilderTest extends XmlSpecification {
+    final JavadocConverter javadocConverter = Mock()
+    final GenerationListener listener = Mock()
+    final ClassDocPropertiesBuilder builder = new ClassDocPropertiesBuilder(javadocConverter, listener)
+
+    def buildsPropertiesForClass() {
+        ClassMetaData classMetaData = classMetaData()
+        PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
+        PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
+        ClassDoc superDoc = classDoc()
+        PropertyDoc propertyDocA = propertyDoc('a')
+        PropertyDoc propertyDocC = propertyDoc('c')
+
+        def content = parse('''
+<section>
+    <section><title>Properties</title>
+        <table>
+            <thead><tr><td>Name</td></tr></thead>
+            <tr><td>b</td></tr>
+            <tr><td>a</td></tr>
+        </table>
+    </section>
+    <section><title>Methods</title><table><thead><tr></tr></thead></table></section>
+</section>
+''')
+
+        when:
+        ClassDoc doc = withCategories {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
+            doc.superClass = superDoc
+            builder.build(doc)
+            return doc
+        }
+
+        then:
+        doc.classProperties.size() == 3
+        doc.classProperties[0].name == 'a'
+        doc.classProperties[1].name == 'b'
+        doc.classProperties[2].name == 'c'
+
+        _ * classMetaData.findProperty('b') >> propertyB
+        _ * classMetaData.findProperty('a') >> propertyA
+        _ * classMetaData.superClassName >> 'org.gradle.SuperType'
+        _ * superDoc.getClassProperties() >> [propertyDocC, propertyDocA]
+    }
+
+    def canAttachAdditionalValuesToProperty() {
+        ClassMetaData classMetaData = classMetaData()
+        PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
+        PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
+        ClassDoc superDoc = classDoc()
+        ExtraAttributeDoc inheritedValue = new ExtraAttributeDoc(parse('<td>inherited</td>'), parse('<td>inherited</td>'))
+        ExtraAttributeDoc overriddenValue = new ExtraAttributeDoc(parse('<td>general value</td>'), parse('<td>general</td>'))
+        PropertyDoc inheritedPropertyA = propertyDoc('a', additionalValues: [inheritedValue, overriddenValue])
+        PropertyDoc inheritedPropertyB = propertyDoc('b', additionalValues: [inheritedValue, overriddenValue])
+        PropertyDoc inheritedPropertyC = propertyDoc('c', additionalValues: [inheritedValue, overriddenValue])
+
+        def content = parse('''
+<section>
+    <section><title>Properties</title>
+        <table>
+            <thead><tr><td>Name</td><td>inherited</td><td>added</td><td>overridden <overrides>general value</overrides></td></tr></thead>
+            <tr><td>a</td><td>specific1</td><td>specific2</td><td>specific3</td></tr>
+            <tr><td>b</td><td></td><td/><td/></tr>
+        </table>
+    </section>
+    <section><title>Methods</title><table><thead><tr></tr></thead></table></section>
+</section>
+''')
+
+        when:
+        ClassDoc doc = withCategories {
+            def doc = new ClassDoc('org.gradle.Class', content, document, classMetaData, null)
+            doc.superClass = superDoc
+            builder.build(doc)
+            return doc
+        }
+
+        then:
+        doc.classProperties.size() == 3
+
+        def prop = doc.classProperties[0]
+        prop.name == 'a'
+        prop.additionalValues.size() == 3
+        format(prop.additionalValues[0].title) == 'inherited'
+        format(prop.additionalValues[0].value) == 'specific1'
+        format(prop.additionalValues[1].title) == 'overridden'
+        format(prop.additionalValues[1].value) == 'specific3'
+        format(prop.additionalValues[2].title) == 'added'
+        format(prop.additionalValues[2].value) == 'specific2'
+
+        def prop2 = doc.classProperties[1]
+        prop2.name == 'b'
+        prop2.additionalValues.size() == 2
+        format(prop2.additionalValues[0].title) == 'inherited'
+        format(prop2.additionalValues[0].value) == 'inherited'
+        format(prop2.additionalValues[1].title) == 'overridden'
+        format(prop2.additionalValues[1].value) == 'general'
+
+        def prop3 = doc.classProperties[2]
+        prop3.name == 'c'
+        prop3.additionalValues.size() == 2
+        format(prop3.additionalValues[0].title) == 'inherited'
+        format(prop3.additionalValues[0].value) == 'inherited'
+        format(prop3.additionalValues[1].title) == 'overridden'
+        format(prop3.additionalValues[1].value) == 'general'
+
+        _ * classMetaData.findProperty('b') >> propertyB
+        _ * classMetaData.findProperty('a') >> propertyA
+        _ * classMetaData.superClassName >> 'org.gradle.SuperType'
+        _ * superDoc.classProperties >> [inheritedPropertyA, inheritedPropertyB, inheritedPropertyC]
+    }
+
+    def classMetaData(String name = 'org.gradle.Class') {
+        ClassMetaData classMetaData = Mock()
+        _ * classMetaData.className >> name
+        return classMetaData
+    }
+
+    def classDoc(String name = 'org.gradle.Class') {
+        ClassDoc doc = Mock()
+        _ * doc.name >> name
+        _ * doc.toString() >> "ClassDoc '$name'"
+        return doc
+    }
+
+    def property(String name, ClassMetaData classMetaData) {
+        return property([:], name, classMetaData)
+    }
+
+    def property(Map<String, ?> args, String name, ClassMetaData classMetaData) {
+        PropertyMetaData property = Mock()
+        _ * property.name >> name
+        _ * property.ownerClass >> classMetaData
+        def type = args.type instanceof TypeMetaData ? args.type : new TypeMetaData(args.type ?: 'org.gradle.Type')
+        _ * property.type >> type
+        _ * property.signature >> "$name-signature"
+        _ * javadocConverter.parse(property, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
+        return property
+    }
+
+    def propertyDoc(Map<String, ?> args = [:], String name) {
+        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues ?: [])
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRendererTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRendererTest.groovy
index f43d99e..06824b8 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRendererTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRendererTest.groovy
@@ -16,43 +16,116 @@
 package org.gradle.build.docs.dsl.docbook
 
 import org.gradle.build.docs.XmlSpecification
-import org.gradle.build.docs.dsl.model.PropertyMetaData
-import org.gradle.build.docs.dsl.model.TypeMetaData
-import org.gradle.build.docs.dsl.model.MethodMetaData
-import org.gradle.build.docs.dsl.model.ParameterMetaData
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
+import org.gradle.build.docs.dsl.source.model.ParameterMetaData
+import org.gradle.build.docs.dsl.docbook.model.BlockDoc
+import org.gradle.build.docs.dsl.docbook.model.ExtraAttributeDoc
+import org.gradle.build.docs.dsl.docbook.model.MethodDoc
+import org.gradle.build.docs.dsl.docbook.model.PropertyDoc
+import org.gradle.build.docs.dsl.docbook.model.ClassDoc
+import org.gradle.build.docs.dsl.docbook.model.ClassExtensionDoc
 
 class ClassDocRendererTest extends XmlSpecification {
     final LinkRenderer linkRenderer = linkRenderer()
     final ClassDocRenderer renderer = new ClassDocRenderer(linkRenderer)
 
+    def rendersContentForEmptyClass() {
+        def sourceContent = parse('''
+            <chapter>
+                <section><title>Properties</title></section>
+            </chapter>
+        ''')
+
+        ClassDoc classDoc = classDoc('org.gradle.Class', id: 'classId', content: sourceContent, comment: 'class comment')
+        _ * classDoc.classProperties >> []
+        _ * classDoc.classMethods >> []
+        _ * classDoc.classBlocks >> []
+        _ * classDoc.classExtensions >> []
+
+        when:
+        def result = parse('<root/>')
+        withCategories {
+            renderer.mergeContent(classDoc, result)
+        }
+
+        then:
+        formatTree(result) == '''<root>
+    <chapter id="classId">
+        <title>Class</title>
+        <segmentedlist>
+            <segtitle>API Documentation</segtitle>
+            <seglistitem>
+                <seg>
+                    <apilink class="org.gradle.Class" style="java"/>
+                </seg>
+            </seglistitem>
+        </segmentedlist>
+        <para>class comment</para>
+        <section>
+            <title>Properties</title>
+            <para>No properties</para>
+        </section>
+        <section>
+            <title>Script blocks</title>
+            <para>No script blocks</para>
+        </section>
+        <section>
+            <title>Methods</title>
+            <para>No methods</para>
+        </section>
+    </chapter>
+</root>'''
+    }
+
     def mergesClassMetaDataIntoMainSection() {
-        def content = parse('''
+        def sourceContent = parse('''
             <chapter>
                 <para>Some custom content</para>
             </chapter>
         ''')
 
-        ClassDoc classDoc = classDoc('org.gradle.Class', id: 'classId', content: content, comment: 'class comment')
+        ClassDoc classDoc = classDoc('org.gradle.Class', id: 'classId', content: sourceContent, comment: 'class comment')
+        _ * classDoc.classProperties >> []
+        _ * classDoc.classMethods >> []
+        _ * classDoc.classBlocks >> []
+        _ * classDoc.classExtensions >> []
 
         when:
+        def result = parse('<root/>')
         withCategories {
-            renderer.mergeDescription(classDoc)
+            renderer.mergeContent(classDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter id="classId">
-    <title>Class</title>
-    <segmentedlist>
-        <segtitle>API Documentation</segtitle>
-        <seglistitem>
-            <seg>
-                <apilink class="org.gradle.Class" style="java"/>
-            </seg>
-        </seglistitem>
-    </segmentedlist>
-    <para>class comment</para>
-    <para>Some custom content</para>
-</chapter>'''
+        formatTree(result) == '''<root>
+    <chapter id="classId">
+        <title>Class</title>
+        <segmentedlist>
+            <segtitle>API Documentation</segtitle>
+            <seglistitem>
+                <seg>
+                    <apilink class="org.gradle.Class" style="java"/>
+                </seg>
+            </seglistitem>
+        </segmentedlist>
+        <para>class comment</para>
+        <para>Some custom content</para>
+        <section>
+            <title>Properties</title>
+            <para>No properties</para>
+        </section>
+        <section>
+            <title>Script blocks</title>
+            <para>No script blocks</para>
+        </section>
+        <section>
+            <title>Methods</title>
+            <para>No methods</para>
+        </section>
+    </chapter>
+</root>'''
     }
     
     def mergesPropertyMetaDataIntoPropertiesSection() {
@@ -67,18 +140,20 @@ class ClassDocRendererTest extends XmlSpecification {
             </chapter>
         ''')
 
+        def extraAttribute = new ExtraAttributeDoc(parse('<td>Extra column</td>'), parse('<td>some value</td>'))
         ClassDoc classDoc = classDoc('Class', content: content)
-        PropertyDoc propDoc = propertyDoc('propName', id: 'propId', description: 'prop description', comment: 'prop comment', type: 'org.gradle.Type')
+        PropertyDoc propDoc = propertyDoc('propName', id: 'propId', description: 'prop description', comment: 'prop comment', type: 'org.gradle.Type', attributes: [extraAttribute])
         _ * classDoc.classProperties >> [propDoc]
-        _ * propDoc.additionalValues >> [new ExtraAttributeDoc(parse('<td>Extra column</td>'), parse('<td>some value</td>'))]
+        _ * classDoc.classExtensions >> []
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeProperties(classDoc)
+            renderer.mergeProperties(classDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Properties</title>
         <table>
@@ -117,30 +192,83 @@ class ClassDocRendererTest extends XmlSpecification {
 </chapter>'''
     }
 
-    def removesPropertiesTableWhenClassHasNoProperties() {
+    def rendersDeprecatedAndIncubatingProperties() {
         def content = parse('''
             <chapter>
                 <section><title>Properties</title>
                     <table>
                         <thead><tr><td>Name</td></tr></thead>
+                        <tr><td>deprecatedProperty</td></tr>
+                        <tr><td>incubatingProperty</td></tr>
                     </table>
                 </section>
             </chapter>
         ''')
 
         ClassDoc classDoc = classDoc('Class', content: content)
-        _ * classDoc.classProperties >> []
+        PropertyDoc deprecatedProp = propertyDoc('deprecatedProperty', id: 'prop1', description: 'prop1 description', comment: 'prop1 comment', type: 'org.gradle.Type', deprecated: true)
+        PropertyDoc incubatingProp = propertyDoc('incubatingProperty', id: 'prop2', description: 'prop2 description', comment: 'prop2 comment', type: 'org.gradle.Type', incubating: true)
+        _ * classDoc.classProperties >> [deprecatedProp, incubatingProp]
+        _ * classDoc.classExtensions >> []
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeProperties(classDoc)
+            renderer.mergeProperties(classDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Properties</title>
-        <para>No properties</para>
+        <table>
+            <title>Properties - Class</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend="prop1">
+                        <literal>deprecatedProperty</literal>
+                    </link>
+                </td>
+                <td>
+                    <caution>Deprecated</caution>
+                    <para>prop1 description</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="prop2">
+                        <literal>incubatingProperty</literal>
+                    </link>
+                </td>
+                <td>
+                    <caution>Incubating</caution>
+                    <para>prop2 description</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Property details</title>
+        <section id="prop1" role="detail">
+            <title><classname>org.gradle.Type</classname> <literal>deprecatedProperty</literal> (read-only)</title>
+            <caution>
+                <para>Note: This property is <ulink url="../userguide/feature_lifecycle.html">deprecated</ulink> and will be removed in the next major version of Gradle.</para>
+            </caution>
+            <para>prop1 comment</para>
+        </section>
+        <section id="prop2" role="detail">
+            <title><classname>org.gradle.Type</classname> <literal>incubatingProperty</literal> (read-only)</title>
+            <caution>
+                <para>Note: This property is <ulink url="../userguide/feature_lifecycle.html">incubating</ulink> and may change in a future version of Gradle.</para>
+            </caution>
+            <para>prop2 comment</para>
+        </section>
     </section>
 </chapter>'''
     }
@@ -159,25 +287,20 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc targetClassDoc = classDoc('Class', content: content)
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         PropertyDoc propertyDoc = propertyDoc('propName', id: 'propId')
+        _ * targetClassDoc.classProperties >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
         _ * extensionDoc.extensionProperties >> [propertyDoc]
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeExtensionProperties(targetClassDoc)
+            renderer.mergeProperties(targetClassDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Properties</title>
-        <table>
-            <thead>
-                <tr>
-                    <td>Name</td>
-                </tr>
-            </thead>
-        </table>
         <section>
             <title>Properties added by the <literal>thingo</literal> plugin</title>
             <titleabbrev><literal>thingo</literal> plugin</titleabbrev>
@@ -228,15 +351,16 @@ class ClassDocRendererTest extends XmlSpecification {
         MethodDoc method1 = methodDoc('methodName', id: 'method1Id', returnType: 'ReturnType1', description: 'method description', comment: 'method comment')
         MethodDoc method2 = methodDoc('methodName', id: 'method2Id', returnType: 'ReturnType2', description: 'overloaded description', comment: 'overloaded comment', paramTypes: ['ParamType'])
         _ * classDoc.classMethods >> [method1, method2]
-
+        _ * classDoc.classExtensions >> []
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeMethods(classDoc)
+            renderer.mergeMethods(classDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Methods</title>
         <table>
@@ -279,6 +403,83 @@ class ClassDocRendererTest extends XmlSpecification {
 </chapter>'''
     }
 
+    def rendersDeprecatedAndIncubatingMethods() {
+        def content = parse('''
+            <chapter>
+                <section><title>Methods</title>
+                    <table>
+                        <thead><tr><td>Name</td></tr></thead>
+                        <tr><td>deprecated</td></tr>
+                        <tr><td>incubating</td></tr>
+                    </table>
+                </section>
+            </chapter>
+        ''')
+
+        ClassDoc classDoc = classDoc('Class', content: content)
+        MethodDoc method1 = methodDoc('deprecated', id: 'method1Id', returnType: 'ReturnType1', description: 'method description', comment: 'method comment', deprecated: true)
+        MethodDoc method2 = methodDoc('incubating', id: 'method2Id', returnType: 'ReturnType2', description: 'overloaded description', comment: 'overloaded comment', paramTypes: ['ParamType'], incubating: true)
+        _ * classDoc.classMethods >> [method1, method2]
+        _ * classDoc.classExtensions >> []
+
+        when:
+        def result = parse('<chapter/>', document)
+        withCategories {
+            renderer.mergeMethods(classDoc, result)
+        }
+
+        then:
+        formatTree(result) == '''<chapter>
+    <section>
+        <title>Methods</title>
+        <table>
+            <title>Methods - Class</title>
+            <thead>
+                <tr>
+                    <td>Method</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal><link linkend="method1Id">deprecated</link>()</literal>
+                </td>
+                <td>
+                    <caution>Deprecated</caution>
+                    <para>method description</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><link linkend="method2Id">incubating</link>(p)</literal>
+                </td>
+                <td>
+                    <caution>Incubating</caution>
+                    <para>overloaded description</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Method details</title>
+        <section id="method1Id" role="detail">
+            <title><classname>ReturnType1</classname> <literal>deprecated</literal>()</title>
+            <caution>
+                <para>Note: This method is <ulink url="../userguide/feature_lifecycle.html">deprecated</ulink> and will be removed in the next major version of Gradle.</para>
+            </caution>
+            <para>method comment</para>
+        </section>
+        <section id="method2Id" role="detail">
+            <title><classname>ReturnType2</classname> <literal>incubating</literal>(<classname>ParamType</classname> p)</title>
+            <caution>
+                <para>Note: This method is <ulink url="../userguide/feature_lifecycle.html">incubating</ulink> and may change in a future version of Gradle.</para>
+            </caution>
+            <para>overloaded comment</para>
+        </section>
+    </section>
+</chapter>'''
+    }
+
     def mergesExtensionMethodMetaDataIntoMethodsSection() {
         def content = parse('''
             <chapter>
@@ -294,25 +495,20 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc targetClassDoc = classDoc('Class', content: content)
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         MethodDoc methodDoc = methodDoc('methodName', id: 'methodId')
+        _ * targetClassDoc.classMethods >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
         _ * extensionDoc.extensionMethods >> [methodDoc]
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeExtensionMethods(targetClassDoc)
+            renderer.mergeMethods(targetClassDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Methods</title>
-        <table>
-            <thead>
-                <tr>
-                    <td>Name</td>
-                </tr>
-            </thead>
-        </table>
         <section>
             <title>Methods added by the <literal>thingo</literal> plugin</title>
             <titleabbrev><literal>thingo</literal> plugin</titleabbrev>
@@ -345,34 +541,6 @@ class ClassDocRendererTest extends XmlSpecification {
 </chapter>'''
     }
 
-    def removesMethodsTableWhenClassHasNoMethods() {
-        def content = parse('''
-            <chapter>
-                <section><title>Methods</title>
-                    <table>
-                        <thead><tr><td>Name</td></tr></thead>
-                    </table>
-                </section>
-            </chapter>
-        ''')
-
-        ClassDoc classDoc = classDoc('Class', content: content)
-        _ * classDoc.classMethods >> []
-
-        when:
-        withCategories {
-            renderer.mergeMethods(classDoc)
-        }
-
-        then:
-        formatTree(content) == '''<chapter>
-    <section>
-        <title>Methods</title>
-        <para>No methods</para>
-    </section>
-</chapter>'''
-    }
-
     def mergesBlockMetaDataIntoBlocksSection() {
         def content = parse('''
             <chapter>
@@ -382,16 +550,19 @@ class ClassDocRendererTest extends XmlSpecification {
             </chapter>
         ''')
         ClassDoc classDoc = classDoc('Class', content: content)
-        BlockDoc block = blockDoc('block', id: 'blockId', description: 'block description', comment: 'block comment', type: 'org.gradle.Type')
-        _ * classDoc.classBlocks >> [block]
+        BlockDoc block1 = blockDoc('block1', id: 'block1', description: 'block1 description', comment: 'block1 comment', type: 'org.gradle.Type')
+        BlockDoc block2 = blockDoc('block2', id: 'block2', description: 'block2 description', comment: 'block2 comment', type: 'org.gradle.Type', multivalued: true)
+        _ * classDoc.classBlocks >> [block1, block2]
+        _ * classDoc.classExtensions >> []
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeBlocks(classDoc)
+            renderer.mergeBlocks(classDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Script blocks</title>
         <table>
@@ -404,32 +575,50 @@ class ClassDocRendererTest extends XmlSpecification {
             </thead>
             <tr>
                 <td>
-                    <link linkend="blockId">
-                        <literal>block</literal>
+                    <link linkend="block1">
+                        <literal>block1</literal>
+                    </link>
+                </td>
+                <td>
+                    <para>block1 description</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="block2">
+                        <literal>block2</literal>
                     </link>
                 </td>
                 <td>
-                    <para>block description</para>
+                    <para>block2 description</para>
                 </td>
             </tr>
         </table>
     </section>
     <section>
         <title>Script block details</title>
-        <section id="blockId" role="detail">
-            <title><literal>block</literal> { }</title>
-            <para>block comment</para>
+        <section id="block1" role="detail">
+            <title><literal>block1</literal> { }</title>
+            <para>block1 comment</para>
             <segmentedlist>
                 <segtitle>Delegates to</segtitle>
                 <seglistitem>
-                    <seg><classname>org.gradle.Type</classname> from <link linkend="block">
-                            <literal>block</literal></link></seg>
+                    <seg><classname>org.gradle.Type</classname> from <link linkend="block1">
+                            <literal>block1</literal></link></seg>
+                </seglistitem>
+            </segmentedlist>
+        </section>
+        <section id="block2" role="detail">
+            <title><literal>block2</literal> { }</title>
+            <para>block2 comment</para>
+            <segmentedlist>
+                <segtitle>Delegates to</segtitle>
+                <seglistitem>
+                    <seg>Each <classname>org.gradle.Type</classname> in <link linkend="block2">
+                            <literal>block2</literal></link></seg>
                 </seglistitem>
             </segmentedlist>
         </section>
-    </section>
-    <section>
-        <title>Methods</title>
     </section>
 </chapter>'''
     }
@@ -447,21 +636,20 @@ class ClassDocRendererTest extends XmlSpecification {
         ClassDoc targetClassDoc = classDoc('Class', content: content)
         ClassExtensionDoc extensionDoc = extensionDoc('thingo')
         BlockDoc blockDoc = blockDoc('blockName', id: 'blockId')
+        _ * targetClassDoc.classBlocks >> []
         _ * targetClassDoc.classExtensions >> [extensionDoc]
         _ * extensionDoc.extensionBlocks >> [blockDoc]
 
         when:
+        def result = parse('<chapter/>', document)
         withCategories {
-            renderer.mergeExtensionBlocks(targetClassDoc)
+            renderer.mergeBlocks(targetClassDoc, result)
         }
 
         then:
-        formatTree(content) == '''<chapter>
+        formatTree(result) == '''<chapter>
     <section>
         <title>Script blocks</title>
-        <table>
-            <thead/>
-        </table>
         <section>
             <title>Script blocks added by the <literal>thingo</literal> plugin</title>
             <titleabbrev><literal>thingo</literal> plugin</titleabbrev>
@@ -503,37 +691,10 @@ class ClassDocRendererTest extends XmlSpecification {
 </chapter>'''
     }
 
-    def doesNotAddBlocksTableWhenClassHasNoScriptBlocks() {
-        def content = parse('''
-            <section>
-                <section><title>Methods</title></section>
-            </section>
-        ''')
-
-        ClassDoc classDoc = classDoc('Class', content: content)
-        _ * classDoc.classBlocks >> []
-
-        when:
-        withCategories {
-            renderer.mergeBlocks(classDoc)
-        }
-
-        then:
-        formatTree(content) == '''<section>
-    <section>
-        <title>Script blocks</title>
-        <para>No script blocks</para>
-    </section>
-    <section>
-        <title>Methods</title>
-    </section>
-</section>'''
-    }
-
     def linkRenderer() {
         LinkRenderer renderer = Mock()
         _ * renderer.link(!null, !null) >> {
-            args -> parse("<classname>${args[0].signature}</classname>")
+            args -> parse("<classname>${args[0].signature}</classname>", document)
         }
         return renderer
     }
@@ -576,6 +737,9 @@ class ClassDocRendererTest extends XmlSpecification {
         _ * propDoc.description >> parse("<para>${args.description ?: 'description'}</para>")
         _ * propDoc.comment >> [parse("<para>${args.comment ?: 'comment'}</para>")]
         _ * propDoc.metaData >> propMetaData
+        _ * propDoc.deprecated >> (args.deprecated ?: false)
+        _ * propDoc.incubating >> (args.incubating ?: false)
+        _ * propDoc.additionalValues >> (args.attributes ?: [])
         _ * propMetaData.type >> new TypeMetaData(args.type ?: 'SomeType')
         return propDoc
     }
@@ -588,10 +752,12 @@ class ClassDocRendererTest extends XmlSpecification {
         _ * methodDoc.description >> parse("<para>${args.description ?: 'description'}</para>")
         _ * methodDoc.comment >> [parse("<para>${args.comment ?: 'comment'}</para>")]
         _ * methodDoc.metaData >> metaData
+        _ * methodDoc.deprecated >> (args.deprecated ?: false)
+        _ * methodDoc.incubating >> (args.incubating ?: false)
         _ * metaData.returnType >> new TypeMetaData(args.returnType ?: 'ReturnType')
         def paramTypes = args.paramTypes ?: []
         _ * metaData.parameters >> paramTypes.collect {
-            def param = new ParameterMetaData("p", metaData);
+            def param = new ParameterMetaData("p");
             param.type = new TypeMetaData(it)
             return param
         }
@@ -607,6 +773,7 @@ class ClassDocRendererTest extends XmlSpecification {
         _ * blockDoc.comment >> [parse("<para>${args.comment ?: 'comment'}</para>")]
         _ * blockDoc.type >> new TypeMetaData(args.type ?: 'BlockType')
         _ * blockDoc.blockProperty >> blockPropDoc
+        _ * blockDoc.multiValued >> (args.multivalued ?: false)
         blockDoc
     }
 
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
deleted file mode 100644
index 3a89de8..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocTest.groovy
+++ /dev/null
@@ -1,367 +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 org.gradle.build.docs.XmlSpecification
-import org.gradle.build.docs.dsl.model.*
-
-class ClassDocTest extends XmlSpecification {
-    final JavadocConverter javadocConverter = Mock()
-    final DslDocModel docModel = Mock()
-
-    def buildsPropertiesForClass() {
-        ClassMetaData classMetaData = classMetaData()
-        PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
-        PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
-        ClassDoc superDoc = classDoc()
-        PropertyDoc propertyDocA = propertyDoc('a')
-        PropertyDoc propertyDocC = propertyDoc('c')
-
-        def content = parse('''
-<section>
-    <section><title>Properties</title>
-        <table>
-            <thead><tr><td>Name</td></tr></thead>
-            <tr><td>b</td></tr>
-            <tr><td>a</td></tr>
-        </table>
-    </section>
-    <section><title>Methods</title><table><thead><tr></tr></thead></table></section>
-</section>
-''')
-
-        when:
-        ClassDoc doc = withCategories {
-            new ClassDoc('org.gradle.Class', content, document, classMetaData, null, docModel, javadocConverter).buildProperties()
-        }
-
-        then:
-        doc.classProperties.size() == 3
-        doc.classProperties[0].name == 'a'
-        doc.classProperties[1].name == 'b'
-        doc.classProperties[2].name == 'c'
-
-        _ * classMetaData.findProperty('b') >> propertyB
-        _ * classMetaData.findProperty('a') >> propertyA
-        _ * classMetaData.superClassName >> 'org.gradle.SuperType'
-        _ * docModel.getClassDoc('org.gradle.SuperType') >> superDoc
-        _ * superDoc.getClassProperties() >> [propertyDocC, propertyDocA]
-    }
-
-    def canAttachAdditionalValuesToProperty() {
-        ClassMetaData classMetaData = classMetaData()
-        PropertyMetaData propertyA = property('a', classMetaData, comment: 'prop a')
-        PropertyMetaData propertyB = property('b', classMetaData, comment: 'prop b')
-        ClassDoc superDoc = classDoc()
-        ExtraAttributeDoc inheritedValue = new ExtraAttributeDoc(parse('<td>inherited</td>'), parse('<td>inherited</td>'))
-        ExtraAttributeDoc overriddenValue = new ExtraAttributeDoc(parse('<td>general value</td>'), parse('<td>general</td>'))
-        PropertyDoc inheritedPropertyA = propertyDoc('a', additionalValues: [inheritedValue, overriddenValue])
-        PropertyDoc inheritedPropertyB = propertyDoc('b', additionalValues: [inheritedValue, overriddenValue])
-        PropertyDoc inheritedPropertyC = propertyDoc('c', additionalValues: [inheritedValue, overriddenValue])
-
-        def content = parse('''
-<section>
-    <section><title>Properties</title>
-        <table>
-            <thead><tr><td>Name</td><td>inherited</td><td>added</td><td>overridden <overrides>general value</overrides></td></tr></thead>
-            <tr><td>a</td><td>specific1</td><td>specific2</td><td>specific3</td></tr>
-            <tr><td>b</td><td></td><td/><td/></tr>
-        </table>
-    </section>
-    <section><title>Methods</title><table><thead><tr></tr></thead></table></section>
-</section>
-''')
-
-        when:
-        ClassDoc doc = withCategories {
-            new ClassDoc('org.gradle.Class', content, document, classMetaData, null, docModel, javadocConverter).buildProperties()
-        }
-
-        then:
-        doc.classProperties.size() == 3
-
-        def prop = doc.classProperties[0]
-        prop.name == 'a'
-        prop.additionalValues.size() == 3
-        format(prop.additionalValues[0].title) == 'inherited'
-        format(prop.additionalValues[0].value) == 'specific1'
-        format(prop.additionalValues[1].title) == 'overridden'
-        format(prop.additionalValues[1].value) == 'specific3'
-        format(prop.additionalValues[2].title) == 'added'
-        format(prop.additionalValues[2].value) == 'specific2'
-
-        def prop2 = doc.classProperties[1]
-        prop2.name == 'b'
-        prop2.additionalValues.size() == 2
-        format(prop2.additionalValues[0].title) == 'inherited'
-        format(prop2.additionalValues[0].value) == 'inherited'
-        format(prop2.additionalValues[1].title) == 'overridden'
-        format(prop2.additionalValues[1].value) == 'general'
-
-        def prop3 = doc.classProperties[2]
-        prop3.name == 'c'
-        prop3.additionalValues.size() == 2
-        format(prop3.additionalValues[0].title) == 'inherited'
-        format(prop3.additionalValues[0].value) == 'inherited'
-        format(prop3.additionalValues[1].title) == 'overridden'
-        format(prop3.additionalValues[1].value) == 'general'
-
-        _ * classMetaData.findProperty('b') >> propertyB
-        _ * classMetaData.findProperty('a') >> propertyA
-        _ * classMetaData.superClassName >> 'org.gradle.SuperType'
-        _ * docModel.getClassDoc('org.gradle.SuperType') >> superDoc
-        _ * superDoc.classProperties >> [inheritedPropertyA, inheritedPropertyB, inheritedPropertyC]
-    }
-
-    def buildsMethodsForClass() {
-        ClassMetaData classMetaData = classMetaData()
-        MethodMetaData methodA = method('a', classMetaData)
-        MethodMetaData methodB = method('b', classMetaData)
-        MethodMetaData methodBOverload = method('b', classMetaData)
-        MethodDoc methodAOverridden = methodDoc('a')
-        MethodDoc methodC = methodDoc('c')
-        ClassDoc superClass = classDoc('org.gradle.SuperClass')
-
-        def content = parse('''
-<section>
-    <section><title>Methods</title>
-        <table>
-            <thead><tr><td>Name</td></tr></thead>
-            <tr><td>a</td></tr>
-            <tr><td>b</td></tr>
-        </table>
-    </section>
-    <section><title>Properties</title><table><thead><tr>Name</tr></thead></table></section>
-</section>
-''')
-
-        when:
-        ClassDoc doc = withCategories {
-            new ClassDoc('org.gradle.Class', content, document, classMetaData, null, docModel, javadocConverter).buildMethods()
-        }
-
-        then:
-        doc.classMethods.size() == 4
-
-        doc.classMethods[0].name == 'a'
-        doc.classMethods[1].name == 'b'
-        doc.classMethods[2].name == 'b'
-        doc.classMethods[3].name == 'c'
-
-        _ * classMetaData.declaredMethods >> ([methodA, methodB, methodBOverload] as Set)
-        _ * classMetaData.superClassName >> 'org.gradle.SuperClass'
-        _ * docModel.getClassDoc('org.gradle.SuperClass') >> superClass
-        _ * superClass.classMethods >> [methodC, methodAOverridden]
-    }
-
-    def buildsBlocksForClass() {
-        ClassMetaData classMetaData = classMetaData()
-        PropertyMetaData blockProperty = property('block', classMetaData)
-        MethodMetaData blockMethod = method('block', classMetaData, paramTypes: [Closure.class.name])
-        PropertyMetaData compositeBlockProperty = property('listBlock', classMetaData, type: new TypeMetaData('java.util.List').addTypeArg(new TypeMetaData('BlockType')))
-        MethodMetaData compositeBlockMethod = method('listBlock', classMetaData, paramTypes: [Closure.class.name])
-        MethodMetaData tooManyParams = method('block', classMetaData, paramTypes: ['String', 'boolean'])
-        MethodMetaData notAClosure = method('block', classMetaData, paramTypes: ['String'])
-        MethodMetaData noBlockProperty = method('notBlock', classMetaData, paramTypes: [Closure.class.name])
-        _ * classMetaData.findProperty('block') >> blockProperty
-        _ * classMetaData.findProperty('listBlock') >> compositeBlockProperty
-        _ * classMetaData.declaredMethods >> [blockMethod, compositeBlockMethod, tooManyParams, notAClosure, noBlockProperty]
-
-        def content = parse('''
-<section>
-    <section><title>Methods</title>
-        <table>
-            <thead><tr><td>Name</td></tr></thead>
-            <tr><td>block</td></tr>
-            <tr><td>listBlock</td></tr>
-            <tr><td>notBlock</td></tr>
-        </table>
-    </section>
-    <section><title>Properties</title>
-        <table>
-            <thead><tr><td>Name</td></tr></thead>
-            <tr><td>block</td></tr>
-            <tr><td>listBlock</td></tr>
-        </table>
-    </section>
-</section>
-''')
-
-        when:
-        ClassDoc doc = withCategories {
-            new ClassDoc('org.gradle.Class', content, document, classMetaData, null, docModel, javadocConverter).buildProperties().buildMethods()
-        }
-
-        then:
-        doc.classProperties.size() == 2
-        doc.classProperties[0].name == 'block'
-        doc.classProperties[1].name == 'listBlock'
-
-        doc.classMethods.size() == 3
-
-        doc.classBlocks.size() == 2
-        doc.classBlocks[0].name == 'block'
-        doc.classBlocks[0].type.signature == 'org.gradle.Type'
-        !doc.classBlocks[0].multiValued
-
-        doc.classBlocks[1].name == 'listBlock'
-        doc.classBlocks[1].type.signature == 'BlockType'
-        doc.classBlocks[1].multiValued
-    }
-
-    def buildsExtensionsForClassMixins() {
-        ClassMetaData classMetaData = classMetaData()
-        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')
-        _ * docModel.getClassDoc('org.gradle.ExtensionA1') >> extensionA1
-        _ * docModel.getClassDoc('org.gradle.ExtensionA2') >> extensionA2
-        _ * docModel.getClassDoc('org.gradle.ExtensionB') >> extensionB
-
-        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].mixinClasses == [extensionA1, extensionA2] as Set
-
-        doc.classExtensions[1].pluginId == 'b'
-        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') {
-        ClassMetaData classMetaData = Mock()
-        _ * classMetaData.className >> name
-        return classMetaData
-    }
-
-    def classDoc(String name = 'org.gradle.Class') {
-        ClassDoc doc = Mock()
-        _ * doc.name >> name
-        _ * doc.toString() >> "ClassDoc '$name'"
-        return doc
-    }
-
-    def property(String name, ClassMetaData classMetaData) {
-        return property([:], name, classMetaData)
-    }
-
-    def property(Map<String, ?> args, String name, ClassMetaData classMetaData) {
-        PropertyMetaData property = Mock()
-        _ * property.name >> name
-        _ * property.ownerClass >> classMetaData
-        def type = args.type instanceof TypeMetaData ? args.type : new TypeMetaData(args.type ?: 'org.gradle.Type')
-        _ * property.type >> type
-        _ * property.signature >> "$name-signature"
-        _ * javadocConverter.parse(property, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
-        return property
-    }
-
-    def propertyDoc(Map<String, ?> args = [:], String name) {
-        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues ?: [])
-    }
-
-    def method(String name, ClassMetaData classMetaData) {
-        return method([:], name, classMetaData)
-    }
-
-    def method(Map<String, ?> args, String name, ClassMetaData classMetaData) {
-        MethodMetaData method = Mock()
-        List<String> paramTypes = args.paramTypes ?: []
-        _ * method.name >> name
-        _ * method.overrideSignature >> "$name(${paramTypes.join(', ')})"
-        _ * method.parameters >> paramTypes.collect {
-            def param = new ParameterMetaData("p", method);
-            param.type = new TypeMetaData(it)
-            return param
-        }
-        _ * method.ownerClass >> classMetaData
-        _ * method.returnType >> new TypeMetaData(args.returnType ?: 'ReturnType')
-        _ * javadocConverter.parse(method, !null) >> ({[parse("<para>${args.comment ?: 'comment'}</para>")]} as DocComment)
-        return method
-    }
-
-    def methodDoc(String name) {
-        MethodDoc methodDoc = Mock()
-        _ * methodDoc.name >> name
-        _ * methodDoc.metaData >> method(name, null)
-        _ * methodDoc.forClass(!null) >> methodDoc
-        return methodDoc
-    }
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexerTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexerTest.groovy
new file mode 100644
index 0000000..a839d65
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/HtmlToXmlJavadocLexerTest.groovy
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Specification
+
+class HtmlToXmlJavadocLexerTest extends Specification {
+    def "discards whitespace around block elements"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                                             | transformed
+        "  <p>text</p>   "                                 | "<p>text</p>"
+        "<p>text</p>   <p>text</p>"                        | "<p>text</p><p>text</p>"
+        "<p>text</p>   <h2>text</h2> <table/>"             | "<p>text</p><h2>text</h2><table></table>"
+        "  <table>  <tr>  <td>text</td> </tr>\r\n</table>" | "<table><tr><td>text</td></tr></table>"
+    }
+
+    def "does not discard whitespace around inline elements"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                                             | transformed
+        "  <code>code</code>  "                            | "<p><code>code</code></p>"
+        "<p>  <code>code</code> \ttext\t\t<em>em</em></p>" | "<p>  <code>code</code> \ttext\t\t<em>em</em></p>"
+        "<h1>text</h1> text  \t"                           | "<h1>text</h1><p> text</p>"
+        "<ul><li>  text  <li> text  "                      | "<ul><li>  text  </li><li> text</li></ul>"
+    }
+
+    def "wraps text in an implicit <p> element"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                          | transformed
+        "text"                          | "<p>text</p>"
+        "   "                           | ""
+        "   text "                      | "<p>text</p>"
+        "text <code>inline</code> text" | "<p>text <code>inline</code> text</p>"
+        "<ul>text</ul>"                 | "<ul><p>text</p></ul>"
+    }
+
+    def "wraps inline elements in an implicit <p> element"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                            | transformed
+        "<em>text</em>"                   | "<p><em>text</em></p>"
+        "<em>text</em> <code>text</code>" | "<p><em>text</em> <code>text</code></p>"
+        "<ul><em>text</em></ul>"          | "<ul><p><em>text</em></p></ul>"
+    }
+
+    def "closes implicit <p> element at start of block element"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source              | transformed
+        "text<p>para 2</p>" | "<p>text</p><p>para 2</p>"
+        "text<h2>text</h2>" | "<p>text</p><h2>text</h2>"
+    }
+
+    def "does not add implicit <p> element for elements with inline content"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                                   | transformed
+        "<h1>text</h1>"                          | "<h1>text</h1>"
+        "<h2>text</h2>"                          | "<h2>text</h2>"
+        "<p>text</p>"                            | "<p>text</p>"
+        "<h1><code>text</code></h1>"             | "<h1><code>text</code></h1>"
+        "<table><th>text</th><td>text</td></ul>" | "<table><th>text</th><td>text</td></table>"
+    }
+
+    def "does not add implicit <p> element for anchor elements outside <p> elements"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                            | transformed
+        "<a href='ref'>text</a>"          | "<p><a href='ref'>text</a></p>"
+        "<a name='ref'/> text"            | "<a name='ref'></a><p> text</p>"
+        "<a name='ref'/><h2>heading</h2>" | "<a name='ref'></a><h2>heading</h2>"
+        "<p><a name='ref'/>"              | "<p><a name='ref'></a></p>"
+        "<p><a name='ref'/>text"          | "<p><a name='ref'></a>text</p>"
+        "<ul><a name='ref'/></ul>"        | "<ul><a name='ref'></a></ul>"
+    }
+
+    def "adds implicit end of element at end of input"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source         | transformed
+        "<p>text"      | "<p>text</p>"
+        "<ul><li>text" | "<ul><li>text</li></ul>"
+    }
+
+    def "adds implicit end of element"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                                    | transformed
+        "<p>text<p>text"                          | "<p>text</p><p>text</p>"
+        "<ul><li>text<li>text"                    | "<ul><li>text</li><li>text</li></ul>"
+        "<dl><dt>term<dd>item<dt>term<dt>term"    | "<dl><dt>term</dt><dd>item</dd><dt>term</dt><dt>term</dt></dl>"
+        "<table><tr><th>cell<tr><td>cell<td>cell" | "<table><tr><th>cell</th></tr><tr><td>cell</td><td>cell</td></tr></table>"
+        "<ul><li><ul><li>text<li>text"            | "<ul><li><ul><li>text</li><li>text</li></ul></li></ul>"
+    }
+
+    def "splits para on block content"() {
+        expect:
+        parse(source) == transformed
+
+        where:
+        source                      | transformed
+        "text<ul><li>item</ul>text" | "<p>text</p><ul><li>item</li></ul><p>text</p>"
+        "text<h2>header</h2>text"   | "<p>text</p><h2>header</h2><p>text</p>"
+    }
+
+    def parse(String source) {
+        def lexer = new HtmlToXmlJavadocLexer(new BasicJavadocLexer(new JavadocScanner(source)))
+        def result = new StringBuilder()
+        lexer.visit(new JavadocLexer.TokenVisitor() {
+            @Override
+            void onStartHtmlElement(String name) {
+                result.append("<$name")
+            }
+
+            @Override
+            void onHtmlElementAttribute(String name, String value) {
+                result.append(" $name='$value'")
+            }
+
+            @Override
+            void onStartHtmlElementComplete(String name) {
+                result.append(">")
+            }
+
+            @Override
+            void onEndHtmlElement(String name) {
+                result.append("</$name>")
+            }
+
+            @Override
+            void onText(String text) {
+                result.append(text)
+            }
+        })
+        return result.toString()
+    }
+}
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 ff5041b..9749b9d 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
@@ -16,9 +16,9 @@
 package org.gradle.build.docs.dsl.docbook
 
 import org.gradle.build.docs.XmlSpecification
-import org.gradle.build.docs.dsl.model.ClassMetaData
-import org.gradle.build.docs.dsl.model.PropertyMetaData
-import org.gradle.build.docs.dsl.model.MethodMetaData
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.PropertyMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
 
 class JavadocConverterTest extends XmlSpecification {
     final ClassMetaData classMetaData = Mock()
@@ -287,6 +287,16 @@ literal code</programlisting><para> does something.
         format(result.docbook) == '''<para><literal>text</literal></para>'''
     }
 
+    def convertsDlElementToVariableList() {
+        _ * classMetaData.rawCommentText >> '<dl><dt>term<dd>definition<dt>term 2<dd>definition 2</dd></dl>'
+
+        when:
+        def result = parser.parse(classMetaData, listener)
+
+        then:
+        format(result.docbook) == '''<variablelist><varlistentry><term>term</term><listitem>definition</listitem></varlistentry><varlistentry><term>term 2</term><listitem>definition 2</listitem></varlistentry></variablelist>'''
+    }
+
     def convertsHeadingsToSections() {
         _ * classMetaData.rawCommentText >> '''
 <h2>section1</h2>
@@ -301,12 +311,12 @@ text3
         def result = parser.parse(classMetaData, listener)
 
         then:
-        format(result.docbook) == '''<section><title>section1</title>
+        format(result.docbook) == '''<section><title>section1</title><para>
 text1
-<section><title>section 1.1</title>
+</para><section><title>section 1.1</title><para>
 text2
-</section></section><section><title>section 2</title>
-text3</section>'''
+</para></section></section><section><title>section 2</title><para>
+text3</para></section>'''
     }
 
     def convertsTable() {
@@ -321,10 +331,7 @@ text3</section>'''
         def result = parser.parse(classMetaData, listener)
 
         then:
-        format(result.docbook) == '''<table>
-    <thead><tr><td>column1</td><td>column2</td></tr></thead>
-    <tr><td>cell1</td><td>cell2</td></tr>
-</table>'''
+        format(result.docbook) == '''<table><thead><tr><td>column1</td><td>column2</td></tr></thead><tr><td>cell1</td><td>cell2</td></tr></table>'''
     }
 
     def convertsPropertyGetterMethodCommentToPropertyComment() {
@@ -350,9 +357,11 @@ text3</section>'''
         _ * propertyMetaData.overriddenProperty >> overriddenMetaData
         _ * overriddenMetaData.rawCommentText >> ''' *
  * <em>inherited value</em>
+ * <p>another
  *
 '''
-        format(result.docbook) == '''<para>before </para><para><emphasis>inherited value</emphasis></para><para> after</para>'''
+        format(result.docbook) == '''<para>before </para><para><emphasis>inherited value</emphasis>
+</para><para>another</para><para> after</para>'''
     }
 
     def convertsValueTag() {
@@ -388,7 +397,7 @@ text3</section>'''
         def result = parser.parse(propertyMetaData, listener)
 
         then:
-        format(result.docbook) == '''<para><UNHANDLED-ELEMENT><unknown>text</unknown></UNHANDLED-ELEMENT><UNHANDLED-ELEMENT><inheritdoc><UNHANDLED-TAG>{@unknown text}</UNHANDLED-TAG><UNHANDLED-TAG>{@p text}</UNHANDLED-TAG><UNHANDLED-TAG>{@ unknown}</UNHANDLED-TAG></UNHANDLED-ELEMENT></para>'''
+        format(result.docbook) == '''<para><UNHANDLED-ELEMENT><unknown>text</unknown></UNHANDLED-ELEMENT><UNHANDLED-ELEMENT><inheritdoc><UNHANDLED-TAG>{@unknown text}</UNHANDLED-TAG><UNHANDLED-TAG>{@p text}</UNHANDLED-TAG><UNHANDLED-TAG>{@ unknown}</UNHANDLED-TAG></inheritdoc></UNHANDLED-ELEMENT></para>'''
     }
 
     def handlesMissingStartTags() {
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
index 59af961..4141ede 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.build.docs.dsl.docbook
 
-import org.gradle.build.docs.dsl.TypeNameResolver
+import org.gradle.build.docs.dsl.source.TypeNameResolver
 import org.gradle.build.docs.XmlSpecification
-import org.gradle.build.docs.dsl.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
-import org.gradle.build.docs.dsl.model.MethodMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
 
 class JavadocLinkConverterTest extends XmlSpecification {
     final LinkRenderer linkRenderer = Mock()
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/LinkRendererTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/LinkRendererTest.groovy
index fe1f2cb..de7f750 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/LinkRendererTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/LinkRendererTest.groovy
@@ -16,9 +16,9 @@
 package org.gradle.build.docs.dsl.docbook
 
 import org.gradle.build.docs.XmlSpecification
-import org.gradle.build.docs.dsl.model.TypeMetaData
-import org.gradle.build.docs.dsl.model.MethodMetaData
-import org.gradle.build.docs.dsl.model.ClassMetaData
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+import org.gradle.build.docs.dsl.source.model.MethodMetaData
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
 
 class LinkRendererTest extends XmlSpecification {
     final DslDocModel model = Mock()
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy
deleted file mode 100644
index 1b354f9..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
-
-import spock.lang.Specification
-
-class ClassMetaDataTest extends Specification {
-    def "is deprecated when @Deprecated annotation is attached to class"() {
-        def notDeprecated = new ClassMetaData("SomeClass")
-        def deprecated = new ClassMetaData("SomeClass")
-        deprecated.addAnnotationTypeName(Deprecated.class.name)
-
-        expect:
-        !notDeprecated.deprecated
-        deprecated.deprecated
-    }
-
-    def "is experimental when @Experimental annotation is attached to class"() {
-        def notExperimental = new ClassMetaData("SomeClass")
-        def experimental = new ClassMetaData("SomeClass")
-        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
-
-        expect:
-        !notExperimental.experimental
-        experimental.experimental
-    }
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy
deleted file mode 100644
index 39e0f87..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy
+++ /dev/null
@@ -1,163 +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.model
-
-import spock.lang.Specification
-
-class MethodMetaDataTest extends Specification {
-    final ClassMetaData owner = Mock()
-    final MethodMetaData method = new MethodMetaData('method', owner)
-
-    def formatsSignature() {
-        method.returnType = new TypeMetaData('ReturnType')
-        method.addParameter('param1', new TypeMetaData('ParamType'))
-        method.addParameter('param2', new TypeMetaData('ParamType2'))
-
-        expect:
-        method.signature == 'ReturnType method(ParamType param1, ParamType2 param2)'
-    }
-
-    def formatsOverrideSignatureUsingRawParameterTypes() {
-        method.returnType = new TypeMetaData('ReturnType')
-        method.addParameter('param', new TypeMetaData('ParamType').addTypeArg(new TypeMetaData("Type1")))
-        method.addParameter('param2', new TypeMetaData('ParamType2'))
-
-        expect:
-        method.overrideSignature == 'method(ParamType, ParamType2)'
-    }
-
-    def locatesOverriddenMethodInSuperClass() {
-        ClassMetaData superClassMetaData = Mock()
-        MethodMetaData overriddenMethod = Mock()
-
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == overriddenMethod
-        _ * owner.superClass >> superClassMetaData
-        _ * owner.interfaces >> []
-        1 * superClassMetaData.findDeclaredMethod('method()') >> overriddenMethod
-    }
-
-    def locatesOverriddenMethodInDirectlyImplementedInterface() {
-        ClassMetaData interfaceMetaData = Mock()
-        MethodMetaData overriddenMethod = Mock()
-
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == overriddenMethod
-        _ * owner.superClass >> null
-        _ * owner.interfaces >> [interfaceMetaData]
-        1 * interfaceMetaData.findDeclaredMethod('method()') >> overriddenMethod
-    }
-
-    def locatesOverriddenMethodInAncestorClass() {
-        ClassMetaData superClassMetaData = Mock()
-        ClassMetaData ancestorClassMetaData = Mock()
-        MethodMetaData overriddenMethod = Mock()
-
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == overriddenMethod
-        _ * owner.superClass >> superClassMetaData
-        _ * owner.interfaces >> []
-        1 * superClassMetaData.findDeclaredMethod('method()') >> null
-        _ * superClassMetaData.superClass >> ancestorClassMetaData
-        _ * superClassMetaData.interfaces >> []
-        1 * ancestorClassMetaData.findDeclaredMethod('method()') >> overriddenMethod
-    }
-
-    def locatesOverriddenMethodInInterfaceOfAncestorClass() {
-        ClassMetaData superClassMetaData = Mock()
-        ClassMetaData interfaceMetaData = Mock()
-        MethodMetaData overriddenMethod = Mock()
-
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == overriddenMethod
-        _ * owner.superClass >> superClassMetaData
-        _ * owner.interfaces >> []
-        1 * superClassMetaData.findDeclaredMethod('method()') >> null
-        _ * superClassMetaData.superClass >> null
-        _ * superClassMetaData.interfaces >> [interfaceMetaData]
-        1 * interfaceMetaData.findDeclaredMethod('method()') >> overriddenMethod
-    }
-
-    def hasNoOverriddenMethodWhenNoSuperClass() {
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == null
-        _ * owner.superClass >> null
-        _ * owner.interfaces >> []
-    }
-
-    def hasNoOverriddenMethodWhenMethodDoesNotOverrideMethodInSuperClass() {
-        ClassMetaData superClassMetaData = Mock()
-
-        when:
-        def m = method.overriddenMethod
-
-        then:
-        m == null
-        _ * owner.superClass >> superClassMetaData
-        _ * owner.interfaces >> []
-        1 * superClassMetaData.findDeclaredMethod('method()') >> null
-        _ * superClassMetaData.superClass >> null
-        _ * superClassMetaData.interfaces >> []
-    }
-
-    def "is deprecated when @Deprecated is attached to method or owner is deprecated"() {
-        ClassMetaData deprecatedClass = Mock()
-        def notDeprecated = new MethodMetaData('param', owner)
-        def deprecated = new MethodMetaData('param', owner)
-        def ownerDeprecated = new MethodMetaData('param', deprecatedClass)
-
-        given:
-        deprecated.addAnnotationTypeName(Deprecated.class.name)
-        deprecatedClass.deprecated >> true
-
-        expect:
-        !notDeprecated.deprecated
-        deprecated.deprecated
-        ownerDeprecated.deprecated
-    }
-
-    def "is experimental when @Experimental is attached to method or owner is experimental"() {
-        ClassMetaData experimentalClass = Mock()
-        def notExperimental = new MethodMetaData('param', owner)
-        def experimental = new MethodMetaData('param', owner)
-        def ownerExperimental = new MethodMetaData('param', experimentalClass)
-
-        given:
-        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
-        experimentalClass.experimental >> true
-
-        expect:
-        !notExperimental.experimental
-        experimental.experimental
-        ownerExperimental.experimental
-    }
-
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy
deleted file mode 100644
index dd6e0ac..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy
+++ /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.build.docs.dsl.model
-
-import spock.lang.Specification
-
-class ParameterMetaDataTest extends Specification {
-    def "formats signature"() {
-        MethodMetaData method = Mock()
-        def parameter = new ParameterMetaData('param', method)
-        def type = new TypeMetaData('org.gradle.SomeType')
-        parameter.type = type
-        
-        expect:
-        parameter.signature == 'org.gradle.SomeType param'
-    }
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy
deleted file mode 100644
index 5f63788..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy
+++ /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.build.docs.dsl.model
-
-import spock.lang.Specification
-
-class PropertyMetaDataTest extends Specification {
-    final ClassMetaData classMetaData = Mock()
-    final PropertyMetaData propertyMetaData = new PropertyMetaData('prop', classMetaData)
-
-    def formatsSignature() {
-        def type = new TypeMetaData('org.gradle.SomeClass')
-        propertyMetaData.type = type
-
-        expect:
-        propertyMetaData.signature == 'org.gradle.SomeClass prop'
-    }
-
-    def usesGetterToLocateOverriddenProperty() {
-        MethodMetaData getter = Mock()
-        MethodMetaData overriddenGetter = Mock()
-        ClassMetaData overriddenClass = Mock()
-        PropertyMetaData overriddenProperty = Mock()
-        propertyMetaData.getter = getter
-
-        when:
-        def p = propertyMetaData.overriddenProperty
-
-        then:
-        p == overriddenProperty
-        _ * getter.overriddenMethod >> overriddenGetter
-        _ * overriddenGetter.ownerClass >> overriddenClass
-        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
-    }
-
-    def usesSetterToLocateOverriddenPropertyWhenPropertyHasNoGetter() {
-        MethodMetaData setter = Mock()
-        MethodMetaData overriddenSetter = Mock()
-        ClassMetaData overriddenClass = Mock()
-        PropertyMetaData overriddenProperty = Mock()
-        propertyMetaData.setter = setter
-
-        when:
-        def p = propertyMetaData.overriddenProperty
-
-        then:
-        p == overriddenProperty
-        _ * setter.overriddenMethod >> overriddenSetter
-        _ * overriddenSetter.ownerClass >> overriddenClass
-        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
-    }
-
-    def usesSetterToLocateOverriddenPropertyWhenGetterDoesNotOverrideAnything() {
-        MethodMetaData getter = Mock()
-        MethodMetaData setter = Mock()
-        MethodMetaData overriddenSetter = Mock()
-        ClassMetaData overriddenClass = Mock()
-        PropertyMetaData overriddenProperty = Mock()
-        propertyMetaData.getter = getter
-        propertyMetaData.setter = setter
-
-        when:
-        def p = propertyMetaData.overriddenProperty
-
-        then:
-        p == overriddenProperty
-        1 * getter.overriddenMethod >> null
-        _ * setter.overriddenMethod >> overriddenSetter
-        _ * overriddenSetter.ownerClass >> overriddenClass
-        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
-    }
-
-    def hasNoOverriddenPropertyWhenGetterDoesNotOverrideAnythingAndHasNoSetter() {
-        when:
-        def p = propertyMetaData.overriddenProperty
-
-        then:
-        p == null
-    }
-
-    def hasNoOverriddenPropertyWhenGetterAndSetterDoNotOverrideAnything() {
-        when:
-        def p = propertyMetaData.overriddenProperty
-
-        then:
-        p == null
-    }
-
-    def "is deprecated when @Deprecated is attached to property or owner is deprecated"() {
-        ClassMetaData deprecatedClass = Mock()
-        def notDeprecated = new PropertyMetaData('param', classMetaData)
-        def deprecated = new PropertyMetaData('param', classMetaData)
-        def ownerDeprecated = new PropertyMetaData('param', deprecatedClass)
-
-        given:
-        deprecated.addAnnotationTypeName(Deprecated.class.name)
-        deprecatedClass.deprecated >> true
-
-        expect:
-        !notDeprecated.deprecated
-        deprecated.deprecated
-        ownerDeprecated.deprecated
-    }
-
-    def "is experimental when @Experimental is attached to property or owner is experimental"() {
-        ClassMetaData experimentalClass = Mock()
-        def notExperimental = new PropertyMetaData('param', classMetaData)
-        def experimental = new PropertyMetaData('param', classMetaData)
-        def ownerExperimental = new PropertyMetaData('param', experimentalClass)
-
-        given:
-        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
-        experimentalClass.experimental >> true
-
-        expect:
-        !notExperimental.experimental
-        experimental.experimental
-        ownerExperimental.experimental
-    }
-}
-
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/TypeMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/TypeMetaDataTest.groovy
deleted file mode 100644
index 76aaa23..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/TypeMetaDataTest.groovy
+++ /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.build.docs.dsl.model
-
-import spock.lang.Specification
-
-class TypeMetaDataTest extends Specification {
-    final TypeMetaData type = new TypeMetaData('org.gradle.SomeType')
-
-    def rawTypeForSimpleType() {
-        expect:
-        type.rawType.signature == 'org.gradle.SomeType'
-    }
-
-    def rawTypeForArrayType() {
-        type.addArrayDimension()
-        type.addArrayDimension()
-
-        expect:
-        type.rawType.signature == 'org.gradle.SomeType[][]'
-    }
-
-    def rawTypeForVarargsType() {
-        type.setVarargs()
-
-        expect:
-        type.rawType.signature == 'org.gradle.SomeType...'
-    }
-
-    def rawTypeForParameterizedArrayType() {
-        type.addArrayDimension()
-        type.addArrayDimension()
-        type.addTypeArg(new TypeMetaData('Type1'))
-
-        expect:
-        type.rawType.signature == 'org.gradle.SomeType[][]'
-    }
-
-    def rawTypeForParameterizedType() {
-        type.addTypeArg(new TypeMetaData('Type1'))
-        type.addTypeArg(new TypeMetaData('Type2'))
-
-        expect:
-        type.rawType.signature == 'org.gradle.SomeType'
-    }
-
-    def rawTypeForWildcardType() {
-        type.setWildcard()
-
-        expect:
-        type.rawType.signature == 'java.lang.Object'
-    }
-
-    def rawTypeForWildcardWithUpperBound() {
-        type.setUpperBounds(new TypeMetaData('OtherType'))
-
-        expect:
-        type.rawType.signature == 'OtherType'
-    }
-
-    def rawTypeForWildcardWithLowerBound() {
-        type.setLowerBounds(new TypeMetaData('OtherType'))
-
-        expect:
-        type.rawType.signature == 'java.lang.Object'
-    }
-
-    def formatsSignature() {
-        expect:
-        type.signature == 'org.gradle.SomeType'
-    }
-
-    def formatsSignatureForArrayType() {
-        type.addArrayDimension()
-        type.addArrayDimension()
-
-        expect:
-        type.signature == 'org.gradle.SomeType[][]'
-    }
-
-    def formatsSignatureForArrayAndVarargsType() {
-        type.addArrayDimension()
-        type.addArrayDimension()
-        type.setVarargs()
-
-        expect:
-        type.signature == 'org.gradle.SomeType[][]...'
-    }
-
-    def formatsSignatureForParameterizedType() {
-        type.addTypeArg(new TypeMetaData('Type1'))
-        type.addTypeArg(new TypeMetaData('Type2'))
-
-        expect:
-        type.signature == 'org.gradle.SomeType<Type1, Type2>'
-    }
-
-    def formatsSignatureForWildcardType() {
-        type.setWildcard()
-
-        expect:
-        type.signature == '?'
-    }
-
-    def formatsSignatureForWildcardWithUpperBound() {
-        type.setUpperBounds(new TypeMetaData('OtherType'))
-
-        expect:
-        type.signature == '? extends OtherType'
-    }
-
-    def formatsSignatureForWildcardWithLowerBound() {
-        type.setLowerBounds(new TypeMetaData('OtherType'))
-
-        expect:
-        type.signature == '? super OtherType'
-    }
-
-    def visitsSignature() {
-        TypeMetaData.SignatureVisitor visitor = Mock()
-
-        when:
-        type.visitSignature(visitor)
-
-        then:
-        1 * visitor.visitType('org.gradle.SomeType')
-        0 * visitor._
-    }
-
-    def visitsSignatureForArrayType() {
-        TypeMetaData.SignatureVisitor visitor = Mock()
-        type.addArrayDimension()
-        type.addArrayDimension()
-
-        when:
-        type.visitSignature(visitor)
-
-        then:
-        1 * visitor.visitType('org.gradle.SomeType')
-        1 * visitor.visitText('[][]')
-        0 * visitor._
-    }
-
-    def visitsSignatureForParameterizedType() {
-        TypeMetaData.SignatureVisitor visitor = Mock()
-        type.addTypeArg(new TypeMetaData('OtherType'))
-
-        when:
-        type.visitSignature(visitor)
-
-        then:
-        1 * visitor.visitType('org.gradle.SomeType')
-        1 * visitor.visitText('<')
-        1 * visitor.visitType('OtherType')
-        1 * visitor.visitText('>')
-        0 * visitor._
-    }
-
-    def visitsSignatureForWildcardType() {
-        TypeMetaData.SignatureVisitor visitor = Mock()
-        type.setWildcard()
-
-        when:
-        type.visitSignature(visitor)
-
-        then:
-        1 * visitor.visitText('?')
-        0 * visitor._
-    }
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy
new file mode 100644
index 0000000..5c43341
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy
@@ -0,0 +1,717 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source
+
+import org.gradle.api.Project
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.model.SimpleClassMetaDataRepository
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+class ExtractDslMetaDataTaskTest extends Specification {
+    final Project project = new ProjectBuilder().build()
+    final ExtractDslMetaDataTask task = project.tasks.add('dsl', ExtractDslMetaDataTask.class)
+    final SimpleClassMetaDataRepository<ClassMetaData> repository = new SimpleClassMetaDataRepository<ClassMetaData>()
+
+    def setup() {
+        task.destFile = project.file('meta-data.bin')
+    }
+
+    def extractsClassMetaDataFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClass.groovy')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/A.groovy')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+        task.source testFile('org/gradle/test/Interface1.java')
+        task.source testFile('org/gradle/test/Interface2.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClass')
+        groovyClass.groovy
+        !groovyClass.isInterface()
+        groovyClass.rawCommentText.contains('This is a groovy class.')
+        groovyClass.superClassName == 'org.gradle.test.A'
+        groovyClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
+        groovyClass.annotationTypeNames == []
+
+        def groovyInterface = repository.get('org.gradle.test.GroovyInterface')
+        groovyInterface.groovy
+        groovyInterface.isInterface()
+        groovyInterface.superClassName == null
+        groovyInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
+        groovyInterface.annotationTypeNames == []
+    }
+
+    def extractsClassMetaDataFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClass.java')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+        task.source testFile('org/gradle/test/A.groovy')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/Interface1.java')
+        task.source testFile('org/gradle/test/Interface2.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClass')
+        !javaClass.groovy
+        !javaClass.isInterface()
+        javaClass.rawCommentText.contains('This is a java class.')
+        javaClass.superClassName == 'org.gradle.test.A'
+        javaClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
+        javaClass.annotationTypeNames == []
+
+        def javaInterface = repository.get('org.gradle.test.JavaInterface')
+        !javaInterface.groovy
+        javaInterface.isInterface()
+        javaInterface.superClassName == null
+        javaInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
+        javaInterface.annotationTypeNames == []
+    }
+
+    def extractsPropertyMetaDataFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClass.groovy')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClass')
+        groovyClass.declaredPropertyNames == ['readOnly', 'writeOnly', 'someProp', 'groovyProp', 'readOnlyGroovyProp', 'arrayProp'] as Set
+
+        def readOnly = groovyClass.findDeclaredProperty('readOnly')
+        readOnly.type.signature == 'java.lang.Object'
+        readOnly.rawCommentText.contains('A read-only property.')
+        !readOnly.writeable
+        readOnly.getter.rawCommentText.contains('A read-only property.')
+        !readOnly.setter
+
+        def writeOnly = groovyClass.findDeclaredProperty('writeOnly')
+        writeOnly.type.signature == 'org.gradle.test.JavaInterface'
+        writeOnly.rawCommentText.contains('A write-only property.')
+        writeOnly.writeable
+        !writeOnly.getter
+        writeOnly.setter.rawCommentText.contains('A write-only property.')
+
+        def someProp = groovyClass.findDeclaredProperty('someProp')
+        someProp.type.signature == 'org.gradle.test.GroovyInterface'
+        someProp.rawCommentText.contains('A property.')
+        someProp.writeable
+        someProp.getter.rawCommentText.contains('A property.')
+        someProp.setter.rawCommentText == ''
+
+        def groovyProp = groovyClass.findDeclaredProperty('groovyProp')
+        groovyProp.type.signature == 'org.gradle.test.GroovyInterface'
+        groovyProp.rawCommentText.contains('A groovy property.')
+        groovyProp.writeable
+        groovyProp.getter.rawCommentText == ''
+        groovyProp.setter.rawCommentText == ''
+
+        def readOnlyGroovyProp = groovyClass.findDeclaredProperty('readOnlyGroovyProp')
+        readOnlyGroovyProp.type.signature == 'java.lang.String'
+        readOnlyGroovyProp.rawCommentText.contains('A read-only groovy property.')
+        !readOnlyGroovyProp.writeable
+        readOnlyGroovyProp.getter.rawCommentText == ''
+        !readOnlyGroovyProp.setter
+
+        def arrayProp = groovyClass.findDeclaredProperty('arrayProp')
+        arrayProp.type.signature == 'java.lang.String[]'
+        arrayProp.rawCommentText.contains('An array property.')
+        arrayProp.writeable
+        arrayProp.getter.rawCommentText == ''
+        arrayProp.setter.rawCommentText == ''
+    }
+
+    def extractsPropertyMetaDataFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClass.java')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClass')
+        javaClass.declaredPropertyNames == ['readOnly', 'writeOnly', 'someProp', 'flag', 'arrayProp'] as Set
+
+        def readOnly = javaClass.findDeclaredProperty('readOnly')
+        readOnly.type.signature == 'java.lang.String'
+        readOnly.rawCommentText.contains('A read-only property.')
+        !readOnly.writeable
+        readOnly.getter.rawCommentText.contains('A read-only property.')
+        !readOnly.setter
+
+        def writeOnly = javaClass.findDeclaredProperty('writeOnly')
+        writeOnly.type.signature == 'org.gradle.test.JavaInterface'
+        writeOnly.rawCommentText.contains('A write-only property.')
+        writeOnly.writeable
+        !writeOnly.getter
+        writeOnly.setter.rawCommentText.contains('A write-only property.')
+
+        def someProp = javaClass.findDeclaredProperty('someProp')
+        someProp.type.signature == 'org.gradle.test.JavaInterface'
+        someProp.rawCommentText.contains('A property.')
+        someProp.writeable
+        someProp.getter.rawCommentText.contains('A property.')
+        someProp.setter.rawCommentText.contains('The setter for a property.')
+
+        def flag = javaClass.findDeclaredProperty('flag')
+        flag.type.signature == 'boolean'
+        flag.rawCommentText.contains('A boolean property.')
+        !flag.writeable
+        flag.getter.rawCommentText.contains('A boolean property.')
+        !flag.setter
+
+        def arrayProp = javaClass.findDeclaredProperty('arrayProp')
+        arrayProp.type.signature == 'org.gradle.test.JavaInterface[][][]'
+        arrayProp.rawCommentText.contains('An array property.')
+        !arrayProp.writeable
+        arrayProp.getter.rawCommentText.contains('An array property.')
+        !arrayProp.setter
+    }
+
+    def extractsMethodMetaDataFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithMethods.groovy')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithMethods')
+        groovyClass.declaredMethods.collect { it.name } as Set == ['stringMethod', 'refTypeMethod', 'defMethod', 'voidMethod', 'arrayMethod', 'setProp', 'getProp', 'getFinalProp', 'getIntProp', 'setIntProp'] as Set
+
+        def stringMethod = groovyClass.declaredMethods.find { it.name == 'stringMethod' }
+        stringMethod.rawCommentText.contains('A method that returns String')
+        stringMethod.returnType.signature == 'java.lang.String'
+        stringMethod.parameters.collect { it.name } == ['stringParam']
+        stringMethod.parameters[0].name == 'stringParam'
+        stringMethod.parameters[0].type.signature == 'java.lang.String'
+
+        def refTypeMethod = groovyClass.declaredMethods.find { it.name == 'refTypeMethod' }
+        refTypeMethod.rawCommentText.contains('A method that returns a reference type.')
+        refTypeMethod.returnType.signature == 'org.gradle.test.GroovyInterface'
+        refTypeMethod.parameters.collect { it.name } == ['someThing', 'aFlag']
+        refTypeMethod.parameters[0].name == 'someThing'
+        refTypeMethod.parameters[0].type.signature == 'org.gradle.test.JavaInterface'
+        refTypeMethod.parameters[1].name == 'aFlag'
+        refTypeMethod.parameters[1].type.signature == 'boolean'
+
+        def defMethod = groovyClass.declaredMethods.find { it.name == 'defMethod' }
+        defMethod.rawCommentText.contains('A method that returns a default type.')
+        defMethod.returnType.signature == 'java.lang.Object'
+        defMethod.parameters.collect { it.name } == ['defParam']
+        defMethod.parameters[0].name == 'defParam'
+        defMethod.parameters[0].type.signature == 'java.lang.Object'
+
+        def voidMethod = groovyClass.declaredMethods.find { it.name == 'voidMethod' }
+        voidMethod.rawCommentText.contains('A method that returns void.')
+        voidMethod.returnType.signature == 'void'
+        voidMethod.parameters.collect { it.name } == []
+
+        def arrayMethod = groovyClass.declaredMethods.find { it.name == 'arrayMethod' }
+        arrayMethod.returnType.signature == 'java.lang.String[][]'
+        arrayMethod.returnType.arrayDimensions == 2
+        arrayMethod.parameters.collect { it.name } == ['strings']
+        arrayMethod.parameters[0].name == 'strings'
+        arrayMethod.parameters[0].type.signature == 'java.lang.String[]...'
+        arrayMethod.parameters[0].type.arrayDimensions == 2
+
+        def getProp = groovyClass.declaredMethods.find { it.name == 'getProp' }
+        getProp.rawCommentText == ''
+        getProp.returnType.signature == 'java.lang.String'
+        getProp.parameters.collect { it.name } == []
+
+        def setProp = groovyClass.declaredMethods.find { it.name == 'setProp' }
+        setProp.rawCommentText == ''
+        setProp.returnType.signature == 'void'
+        setProp.parameters.collect { it.name } == ['prop']
+        setProp.parameters[0].name == 'prop'
+        setProp.parameters[0].type.signature == 'java.lang.String'
+
+        def getFinalProp = groovyClass.declaredMethods.find { it.name == 'getFinalProp' }
+        getFinalProp.rawCommentText == ''
+        getFinalProp.returnType.signature == 'org.gradle.test.JavaInterface'
+        getFinalProp.parameters.collect { it.name } == []
+
+        groovyClass.declaredPropertyNames == ['prop', 'finalProp', 'intProp'] as Set
+    }
+
+    def extractsMethodMetaDataFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithMethods.java')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithMethods')
+        javaClass.declaredMethods.collect { it.name } as Set == ['stringMethod', 'refTypeMethod', 'voidMethod', 'arrayMethod', 'getIntProp', 'setIntProp'] as Set
+
+        def stringMethod = javaClass.declaredMethods.find { it.name == 'stringMethod' }
+        stringMethod.rawCommentText.contains('A method that returns String')
+        stringMethod.returnType.signature == 'java.lang.String'
+        stringMethod.parameters.collect { it.name } == ['stringParam']
+        stringMethod.parameters[0].name == 'stringParam'
+        stringMethod.parameters[0].type.signature == 'java.lang.String'
+
+        def refTypeMethod = javaClass.declaredMethods.find { it.name == 'refTypeMethod' }
+        refTypeMethod.rawCommentText.contains('A method that returns a reference type.')
+        refTypeMethod.returnType.signature == 'org.gradle.test.GroovyInterface'
+        refTypeMethod.parameters.collect { it.name } == ['refParam', 'aFlag']
+        refTypeMethod.parameters[0].name == 'refParam'
+        refTypeMethod.parameters[0].type.signature == 'org.gradle.test.JavaInterface'
+        refTypeMethod.parameters[1].name == 'aFlag'
+        refTypeMethod.parameters[1].type.signature == 'boolean'
+
+        def voidMethod = javaClass.declaredMethods.find { it.name == 'voidMethod' }
+        voidMethod.rawCommentText.contains('A method that returns void.')
+        voidMethod.returnType.signature == 'void'
+        voidMethod.parameters.collect { it.name } == []
+
+        def arrayMethod = javaClass.declaredMethods.find { it.name == 'arrayMethod' }
+        arrayMethod.returnType.signature == 'java.lang.String[][]'
+        arrayMethod.returnType.arrayDimensions == 2
+        arrayMethod.parameters.collect { it.name } == ['strings']
+        arrayMethod.parameters[0].name == 'strings'
+        arrayMethod.parameters[0].type.signature == 'java.lang.String[]...'
+        arrayMethod.parameters[0].type.arrayDimensions == 2
+
+        javaClass.declaredPropertyNames == ['intProp'] as Set
+    }
+
+    def extractsConstantsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithConstants.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithConstants')
+        groovyClass.constants.keySet() == ['INT_CONST', 'STRING_CONST', 'OBJECT_CONST', 'BIG_DECIMAL_CONST'] as Set
+
+        groovyClass.constants['INT_CONST'] == '9'
+        groovyClass.constants['STRING_CONST'] == 'some-string'
+        groovyClass.constants['BIG_DECIMAL_CONST'] == '1.02'
+        groovyClass.constants['OBJECT_CONST'] == null
+    }
+
+    def extractsConstantsFromJavaClassSource() {
+        task.source testFile('org/gradle/test/JavaClassWithConstants.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithConstants')
+        javaClass.constants.keySet() == ['INT_CONST', 'STRING_CONST', 'OBJECT_CONST', 'CHAR_CONST'] as Set
+
+        javaClass.constants['INT_CONST'] == '9'
+        javaClass.constants['STRING_CONST'] == 'some-string'
+        javaClass.constants['CHAR_CONST'] == 'a'
+        javaClass.constants['OBJECT_CONST'] == null
+    }
+
+    def extractsConstantsFromJavaInterfaceSource() {
+        task.source testFile('org/gradle/test/JavaInterfaceWithConstants.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaInterface = repository.get('org.gradle.test.JavaInterfaceWithConstants')
+        javaInterface.constants.keySet() == ['INT_CONST', 'STRING_CONST'] as Set
+
+        javaInterface.constants['INT_CONST'] == '120'
+        javaInterface.constants['STRING_CONST'] == 'some-string'
+    }
+
+    def handlesFullyQualifiedNamesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithFullyQualifiedNames.groovy')
+        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
+        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithFullyQualifiedNames')
+        groovyClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
+        groovyClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'java.lang.Runnable']
+        groovyClass.declaredPropertyNames == ['prop'] as Set
+
+        def prop = groovyClass.findDeclaredProperty('prop')
+        prop.type.signature == 'org.gradle.test.sub.SubJavaInterface'
+    }
+
+    def handlesFullyQualifiedNamesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithFullyQualifiedNames.java')
+        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
+        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithFullyQualifiedNames')
+        javaClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
+        javaClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'java.lang.Runnable']
+        javaClass.declaredPropertyNames == ['prop'] as Set
+
+        def prop = javaClass.findDeclaredProperty('prop')
+        prop.type.signature == 'org.gradle.test.sub.SubJavaInterface'
+    }
+
+    def handlesImportedTypesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithImports.groovy')
+        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
+        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
+        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithImports')
+        groovyClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
+        groovyClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'org.gradle.test.sub2.GroovyInterface']
+        groovyClass.declaredPropertyNames == [] as Set
+    }
+
+    def handlesImportedTypesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithImports.java')
+        task.source testFile('org/gradle/test/sub/SubJavaInterface.java')
+        task.source testFile('org/gradle/test/sub/SubGroovyClass.groovy')
+        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithImports')
+        javaClass.superClassName == 'org.gradle.test.sub.SubGroovyClass'
+        javaClass.interfaceNames == ['org.gradle.test.sub.SubJavaInterface', 'org.gradle.test.sub2.GroovyInterface', 'java.io.Closeable']
+        javaClass.declaredPropertyNames == [] as Set
+    }
+
+    def handlesEnumTypesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyEnum.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyEnum = repository.get('org.gradle.test.GroovyEnum')
+        groovyEnum.groovy
+        !groovyEnum.isInterface()
+    }
+
+    def handlesEnumTypesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaEnum.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaEnum = repository.get('org.gradle.test.JavaEnum')
+        !javaEnum.groovy
+        !javaEnum.isInterface()
+    }
+
+    def handlesAnnotationTypesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyAnnotation.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def annotation = repository.get('org.gradle.test.GroovyAnnotation')
+        annotation.groovy
+        !annotation.isInterface()
+    }
+
+    def handlesAnnotationTypesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def annotation = repository.get('org.gradle.test.JavaAnnotation')
+        !annotation.groovy
+        !annotation.isInterface()
+    }
+
+    def handlesNestedAndAnonymousTypesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithInnerTypes.groovy')
+        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithInnerTypes')
+        groovyClass.interfaceNames == ['org.gradle.test.sub2.GroovyInterface']
+        groovyClass.declaredPropertyNames == ['someProp', 'innerClassProp'] as Set
+
+        def someProp = groovyClass.findDeclaredProperty('someProp')
+        someProp.type.signature == 'org.gradle.test.sub2.GroovyInterface'
+
+        def innerClassProp = groovyClass.findDeclaredProperty('innerClassProp')
+        innerClassProp.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerClass.AnotherInner'
+
+        def innerEnum = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerEnum')
+        innerEnum.rawCommentText.contains('This is an inner enum.')
+
+        def innerClass = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerClass')
+        innerClass.rawCommentText.contains('This is an inner class.')
+        innerClass.declaredPropertyNames == ['enumProp'] as Set
+
+        def enumProp = innerClass.findDeclaredProperty('enumProp')
+        enumProp.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerEnum'
+
+        def anotherInner = repository.get('org.gradle.test.GroovyClassWithInnerTypes.InnerClass.AnotherInner')
+        anotherInner.rawCommentText.contains('This is an inner inner class.')
+        anotherInner.declaredPropertyNames == ['outer'] as Set
+
+        def outer = anotherInner.findDeclaredProperty('outer')
+        outer.type.signature == 'org.gradle.test.GroovyClassWithInnerTypes.InnerClass'
+    }
+
+    def handlesNestedAndAnonymousTypesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithInnerTypes.java')
+        task.source testFile('org/gradle/test/sub2/GroovyInterface.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithInnerTypes')
+        javaClass.interfaceNames == ['org.gradle.test.sub2.GroovyInterface']
+        javaClass.declaredPropertyNames == ['someProp', 'innerClassProp'] as Set
+
+        def someProp = javaClass.findDeclaredProperty('someProp')
+        someProp.type.signature == 'org.gradle.test.sub2.GroovyInterface'
+
+        def innerClassProp = javaClass.findDeclaredProperty('innerClassProp')
+        innerClassProp.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerClass.AnotherInner'
+
+        def innerEnum = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerEnum')
+        innerEnum.rawCommentText.contains('This is an inner enum.')
+
+        def innerClass = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerClass')
+        innerClass.rawCommentText.contains('This is an inner class.')
+        innerClass.declaredPropertyNames == ['enumProp'] as Set
+
+        def enumProp = innerClass.findDeclaredProperty('enumProp')
+        enumProp.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerEnum'
+
+        def anotherInner = repository.get('org.gradle.test.JavaClassWithInnerTypes.InnerClass.AnotherInner')
+        anotherInner.rawCommentText.contains('This is an inner inner class.')
+        anotherInner.declaredPropertyNames == ['outer'] as Set
+
+        def outer = anotherInner.findDeclaredProperty('outer')
+        outer.type.signature == 'org.gradle.test.JavaClassWithInnerTypes.InnerClass'
+    }
+
+    def handlesParameterizedTypesInGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithParameterizedTypes.groovy')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithParameterizedTypes')
+
+        def setProp = groovyClass.findDeclaredProperty('setProp')
+        setProp.type.signature == 'java.util.Set<org.gradle.test.GroovyInterface>'
+
+        def mapProp = groovyClass.findDeclaredProperty('mapProp')
+        mapProp.type.signature == 'java.util.Map<org.gradle.test.GroovyInterface, org.gradle.test.GroovyClassWithParameterizedTypes>'
+
+        def wilcardProp = groovyClass.findDeclaredProperty('wildcardProp')
+        wilcardProp.type.signature == 'java.util.List<?>'
+
+        def upperBoundProp = groovyClass.findDeclaredProperty('upperBoundProp')
+        upperBoundProp.type.signature == 'java.util.List<? extends org.gradle.test.GroovyInterface>'
+
+        def lowerBoundProp = groovyClass.findDeclaredProperty('lowerBoundProp')
+        lowerBoundProp.type.signature == 'java.util.List<? super org.gradle.test.GroovyInterface>'
+
+        def nestedProp = groovyClass.findDeclaredProperty('nestedProp')
+        nestedProp.type.signature == 'java.util.List<? super java.util.Set<? extends java.util.Map<?, org.gradle.test.GroovyInterface[]>>>[]'
+
+        def paramMethod = groovyClass.declaredMethods.find { it.name == 'paramMethod' }
+        paramMethod.returnType.signature == 'T'
+        paramMethod.parameters[0].type.signature == 'T'
+    }
+
+    def handlesParameterizedTypesInJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithParameterizedTypes.java')
+        task.source testFile('org/gradle/test/GroovyInterface.groovy')
+        task.source testFile('org/gradle/test/JavaInterface.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithParameterizedTypes')
+
+        def setProp = javaClass.findDeclaredProperty('setProp')
+        setProp.type.signature == 'java.util.Set<org.gradle.test.GroovyInterface>'
+
+        def mapProp = javaClass.findDeclaredProperty('mapProp')
+        mapProp.type.signature == 'java.util.Map<org.gradle.test.GroovyInterface, org.gradle.test.JavaClassWithParameterizedTypes>'
+
+        def wildcardProp = javaClass.findDeclaredProperty('wildcardProp')
+        wildcardProp.type.signature == 'java.util.List<?>'
+
+        def upperBoundProp = javaClass.findDeclaredProperty('upperBoundProp')
+        upperBoundProp.type.signature == 'java.util.List<? extends org.gradle.test.GroovyInterface>'
+
+        def lowerBoundProp = javaClass.findDeclaredProperty('lowerBoundProp')
+        lowerBoundProp.type.signature == 'java.util.List<? super org.gradle.test.GroovyInterface>'
+
+        def nestedProp = javaClass.findDeclaredProperty('nestedProp')
+        nestedProp.type.signature == 'java.util.List<? super java.util.Set<? extends java.util.Map<?, org.gradle.test.GroovyInterface[]>>>[]'
+
+        def paramMethod = javaClass.declaredMethods.find { it.name == 'paramMethod' }
+        paramMethod.returnType.signature == 'T'
+        paramMethod.parameters[0].type.signature == 'T'
+    }
+
+    def extractsClassAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        groovyClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsClassAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaInterfaceWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        javaClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+
+        def javaInterface = repository.get('org.gradle.test.JavaInterfaceWithAnnotation')
+        javaInterface.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsMethodAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        def method = groovyClass.declaredMethods.find { it.name == 'annotatedMethod' }
+        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsMethodAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        def method = javaClass.declaredMethods.find { it.name == 'annotatedMethod' }
+        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsPropertyAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        def property = groovyClass.declaredProperties.find { it.name == 'annotatedProperty' }
+        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsPropertyAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        def property = javaClass.declaredProperties.find { it.name == 'annotatedProperty' }
+        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def testFile(String fileName) {
+        URL resource = getClass().classLoader.getResource(fileName)
+        assert resource != null: "Could not find resource '$fileName'."
+        assert resource.protocol == 'file'
+        return new File(resource.toURI())
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/TypeNameResolverTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/TypeNameResolverTest.groovy
new file mode 100644
index 0000000..fb576a3
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/TypeNameResolverTest.groovy
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source
+
+import org.gradle.build.docs.dsl.source.model.ClassMetaData
+import org.gradle.build.docs.model.ClassMetaDataRepository
+import spock.lang.Specification
+import org.gradle.build.docs.dsl.source.model.TypeMetaData
+
+class TypeNameResolverTest extends Specification {
+    final ClassMetaDataRepository<ClassMetaData> metaDataRepository = Mock()
+    final ClassMetaData classMetaData = Mock()
+    final TypeNameResolver typeNameResolver = new TypeNameResolver(metaDataRepository)
+
+    def resolvesFullyQualifiedClassName() {
+        when:
+        def name = typeNameResolver.resolve('org.gradle.SomeClass', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass'
+        _ * classMetaData.innerClassNames >> []
+    }
+
+    def resolvesUnqualifiedNameToClassInSamePackage() {
+        when:
+        def name = typeNameResolver.resolve('SomeClass', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> []
+        _ * classMetaData.packageName >> 'org.gradle'
+        _ * metaDataRepository.find('org.gradle.SomeClass') >> classMetaData
+    }
+
+    def resolvesUnqualifiedNameToImportedClass() {
+        when:
+        def name = typeNameResolver.resolve('SomeClass', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> ['org.gradle.SomeClass']
+    }
+
+    def resolvesUnqualifiedNameToImportedPackage() {
+        when:
+        def name = typeNameResolver.resolve('SomeClass', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> ['org.gradle.*']
+        _ * metaDataRepository.find('org.gradle.SomeClass') >> classMetaData
+    }
+
+    def resolvesUnqualifiedNameToInnerClass() {
+        when:
+        def name = typeNameResolver.resolve('Inner', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass.Inner'
+        _ * classMetaData.innerClassNames >> ['org.gradle.SomeClass.Inner']
+        _ * classMetaData.className >> 'org.gradle.SomeClass'
+    }
+
+    def resolvesQualifiedNameToInnerClass() {
+        ClassMetaData innerClass = Mock()
+
+        when:
+        def name = typeNameResolver.resolve('A.B', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass.A.B'
+        _ * classMetaData.innerClassNames >> ['org.gradle.SomeClass.A']
+        _ * classMetaData.className >> 'org.gradle.SomeClass'
+        _ * metaDataRepository.get('org.gradle.SomeClass.A') >> innerClass
+        _ * innerClass.innerClassNames >> ['org.gradle.SomeClass.A.B']
+        _ * innerClass.className >> 'org.gradle.SomeClass.A'
+    }
+
+    def resolvesUnqualifiedNameToOuterClass() {
+        when:
+        def name = typeNameResolver.resolve('Outer', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass.Outer'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.outerClassName >> 'org.gradle.SomeClass.Outer'
+    }
+
+    def resolvesUnqualifiedNameToSiblingClass() {
+        ClassMetaData outerClass = Mock()
+
+        when:
+        def name = typeNameResolver.resolve('Sibling', classMetaData)
+
+        then:
+        name == 'org.gradle.SomeClass.Outer.Sibling'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.outerClassName >> 'org.gradle.SomeClass.Outer'
+        _ * metaDataRepository.get('org.gradle.SomeClass.Outer') >> outerClass
+        _ * outerClass.innerClassNames >> ['org.gradle.SomeClass.Outer.Sibling']
+    }
+
+    def resolvesUnqualifiedNameToJavaLangPackage() {
+        when:
+        def name = typeNameResolver.resolve('String', classMetaData)
+
+        then:
+        name == 'java.lang.String'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> []
+    }
+
+    def resolvesUnqualifiedNameToDefaultPackagesAndClassesInGroovySource() {
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> []
+        _ * classMetaData.groovy >> true
+
+        expect:
+        typeNameResolver.resolve('Set', classMetaData) == 'java.util.Set'
+        typeNameResolver.resolve('File', classMetaData) == 'java.io.File'
+        typeNameResolver.resolve('Closure', classMetaData) == 'groovy.lang.Closure'
+        typeNameResolver.resolve('BigDecimal', classMetaData) == 'java.math.BigDecimal'
+        typeNameResolver.resolve('BigInteger', classMetaData) == 'java.math.BigInteger'
+    }
+
+    def resolvesUnqualifiedNameToImportedJavaPackage() {
+        when:
+        def name = typeNameResolver.resolve('Set', classMetaData)
+
+        then:
+        name == 'java.util.Set'
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> ['java.util.*']
+    }
+
+    def resolvesPrimitiveType() {
+        when:
+        def name = typeNameResolver.resolve('boolean', classMetaData)
+
+        then:
+        name == 'boolean'
+    }
+
+    def resolvesParameterisedTypes() {
+        def typeMetaData = type('SomeClass')
+        typeMetaData.addTypeArg(type('String'))
+
+        when:
+        typeNameResolver.resolve(typeMetaData, classMetaData)
+
+        then:
+        typeMetaData.signature == 'org.gradle.SomeClass<java.lang.String>'
+
+        _ * classMetaData.innerClassNames >> []
+        _ * classMetaData.imports >> ['org.gradle.SomeClass']
+    }
+
+    def type(String name) {
+        return new TypeMetaData(name)
+    }
+}
+
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaDataTest.groovy
new file mode 100644
index 0000000..3338393
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaDataTest.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.build.docs.dsl.source.model
+
+import spock.lang.Specification
+
+class ClassMetaDataTest extends Specification {
+    def "is deprecated when @Deprecated annotation is attached to class"() {
+        def notDeprecated = new ClassMetaData("SomeClass")
+        def deprecated = new ClassMetaData("SomeClass")
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+    }
+
+    def "is incubating when @Incubating annotation is attached to class"() {
+        def notIncubating = new ClassMetaData("SomeClass")
+        def incubating = new ClassMetaData("SomeClass")
+        incubating.addAnnotationTypeName("org.gradle.api.Incubating")
+
+        expect:
+        !notIncubating.incubating
+        incubating.incubating
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaDataTest.groovy
new file mode 100644
index 0000000..0cbf728
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/MethodMetaDataTest.groovy
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source.model
+
+import spock.lang.Specification
+
+class MethodMetaDataTest extends Specification {
+    final ClassMetaData owner = Mock()
+    final MethodMetaData method = new MethodMetaData('method', owner)
+
+    def formatsSignature() {
+        method.returnType = new TypeMetaData('ReturnType')
+        method.addParameter('param1', new TypeMetaData('ParamType'))
+        method.addParameter('param2', new TypeMetaData('ParamType2'))
+
+        expect:
+        method.signature == 'ReturnType method(ParamType param1, ParamType2 param2)'
+    }
+
+    def formatsOverrideSignatureUsingRawParameterTypes() {
+        method.returnType = new TypeMetaData('ReturnType')
+        method.addParameter('param', new TypeMetaData('ParamType').addTypeArg(new TypeMetaData("Type1")))
+        method.addParameter('param2', new TypeMetaData('ParamType2'))
+
+        expect:
+        method.overrideSignature == 'method(ParamType, ParamType2)'
+    }
+
+    def locatesOverriddenMethodInSuperClass() {
+        ClassMetaData superClassMetaData = Mock()
+        MethodMetaData overriddenMethod = Mock()
+
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == overriddenMethod
+        _ * owner.superClass >> superClassMetaData
+        _ * owner.interfaces >> []
+        1 * superClassMetaData.findDeclaredMethod('method()') >> overriddenMethod
+    }
+
+    def locatesOverriddenMethodInDirectlyImplementedInterface() {
+        ClassMetaData interfaceMetaData = Mock()
+        MethodMetaData overriddenMethod = Mock()
+
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == overriddenMethod
+        _ * owner.superClass >> null
+        _ * owner.interfaces >> [interfaceMetaData]
+        1 * interfaceMetaData.findDeclaredMethod('method()') >> overriddenMethod
+    }
+
+    def locatesOverriddenMethodInAncestorClass() {
+        ClassMetaData superClassMetaData = Mock()
+        ClassMetaData ancestorClassMetaData = Mock()
+        MethodMetaData overriddenMethod = Mock()
+
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == overriddenMethod
+        _ * owner.superClass >> superClassMetaData
+        _ * owner.interfaces >> []
+        1 * superClassMetaData.findDeclaredMethod('method()') >> null
+        _ * superClassMetaData.superClass >> ancestorClassMetaData
+        _ * superClassMetaData.interfaces >> []
+        1 * ancestorClassMetaData.findDeclaredMethod('method()') >> overriddenMethod
+    }
+
+    def locatesOverriddenMethodInInterfaceOfAncestorClass() {
+        ClassMetaData superClassMetaData = Mock()
+        ClassMetaData interfaceMetaData = Mock()
+        MethodMetaData overriddenMethod = Mock()
+
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == overriddenMethod
+        _ * owner.superClass >> superClassMetaData
+        _ * owner.interfaces >> []
+        1 * superClassMetaData.findDeclaredMethod('method()') >> null
+        _ * superClassMetaData.superClass >> null
+        _ * superClassMetaData.interfaces >> [interfaceMetaData]
+        1 * interfaceMetaData.findDeclaredMethod('method()') >> overriddenMethod
+    }
+
+    def hasNoOverriddenMethodWhenNoSuperClass() {
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == null
+        _ * owner.superClass >> null
+        _ * owner.interfaces >> []
+    }
+
+    def hasNoOverriddenMethodWhenMethodDoesNotOverrideMethodInSuperClass() {
+        ClassMetaData superClassMetaData = Mock()
+
+        when:
+        def m = method.overriddenMethod
+
+        then:
+        m == null
+        _ * owner.superClass >> superClassMetaData
+        _ * owner.interfaces >> []
+        1 * superClassMetaData.findDeclaredMethod('method()') >> null
+        _ * superClassMetaData.superClass >> null
+        _ * superClassMetaData.interfaces >> []
+    }
+
+    def "is deprecated when @Deprecated is attached to method"() {
+        def notDeprecated = new MethodMetaData('param', owner)
+        def deprecated = new MethodMetaData('param', owner)
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+    }
+
+    def "is incubating when @Incubating is attached to method"() {
+        def notIncubating = new MethodMetaData('param', owner)
+        def incubating = new MethodMetaData('param', owner)
+        incubating.addAnnotationTypeName("org.gradle.api.Incubating")
+
+        expect:
+        !notIncubating.incubating
+        incubating.incubating
+    }
+
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaDataTest.groovy
new file mode 100644
index 0000000..5ef341c
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/ParameterMetaDataTest.groovy
@@ -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.build.docs.dsl.source.model
+
+import spock.lang.Specification
+
+class ParameterMetaDataTest extends Specification {
+    def "formats signature"() {
+        MethodMetaData method = Mock()
+        def parameter = new ParameterMetaData('param')
+        def type = new TypeMetaData('org.gradle.SomeType')
+        parameter.type = type
+        
+        expect:
+        parameter.signature == 'org.gradle.SomeType param'
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaDataTest.groovy
new file mode 100644
index 0000000..0dcde94
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaDataTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source.model
+
+import spock.lang.Specification
+
+class PropertyMetaDataTest extends Specification {
+    final ClassMetaData classMetaData = Mock()
+    final PropertyMetaData propertyMetaData = new PropertyMetaData('prop', classMetaData)
+
+    def formatsSignature() {
+        def type = new TypeMetaData('org.gradle.SomeClass')
+        propertyMetaData.type = type
+
+        expect:
+        propertyMetaData.signature == 'org.gradle.SomeClass prop'
+    }
+
+    def usesGetterToLocateOverriddenProperty() {
+        MethodMetaData getter = Mock()
+        MethodMetaData overriddenGetter = Mock()
+        ClassMetaData overriddenClass = Mock()
+        PropertyMetaData overriddenProperty = Mock()
+        propertyMetaData.getter = getter
+
+        when:
+        def p = propertyMetaData.overriddenProperty
+
+        then:
+        p == overriddenProperty
+        _ * getter.overriddenMethod >> overriddenGetter
+        _ * overriddenGetter.ownerClass >> overriddenClass
+        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
+    }
+
+    def usesSetterToLocateOverriddenPropertyWhenPropertyHasNoGetter() {
+        MethodMetaData setter = Mock()
+        MethodMetaData overriddenSetter = Mock()
+        ClassMetaData overriddenClass = Mock()
+        PropertyMetaData overriddenProperty = Mock()
+        propertyMetaData.setter = setter
+
+        when:
+        def p = propertyMetaData.overriddenProperty
+
+        then:
+        p == overriddenProperty
+        _ * setter.overriddenMethod >> overriddenSetter
+        _ * overriddenSetter.ownerClass >> overriddenClass
+        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
+    }
+
+    def usesSetterToLocateOverriddenPropertyWhenGetterDoesNotOverrideAnything() {
+        MethodMetaData getter = Mock()
+        MethodMetaData setter = Mock()
+        MethodMetaData overriddenSetter = Mock()
+        ClassMetaData overriddenClass = Mock()
+        PropertyMetaData overriddenProperty = Mock()
+        propertyMetaData.getter = getter
+        propertyMetaData.setter = setter
+
+        when:
+        def p = propertyMetaData.overriddenProperty
+
+        then:
+        p == overriddenProperty
+        1 * getter.overriddenMethod >> null
+        _ * setter.overriddenMethod >> overriddenSetter
+        _ * overriddenSetter.ownerClass >> overriddenClass
+        _ * overriddenClass.findDeclaredProperty('prop') >> overriddenProperty
+    }
+
+    def hasNoOverriddenPropertyWhenGetterDoesNotOverrideAnythingAndHasNoSetter() {
+        when:
+        def p = propertyMetaData.overriddenProperty
+
+        then:
+        p == null
+    }
+
+    def hasNoOverriddenPropertyWhenGetterAndSetterDoNotOverrideAnything() {
+        when:
+        def p = propertyMetaData.overriddenProperty
+
+        then:
+        p == null
+    }
+
+    def "is deprecated when @Deprecated is attached to property"() {
+        def notDeprecated = new PropertyMetaData('param', classMetaData)
+        def deprecated = new PropertyMetaData('param', classMetaData)
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+    }
+
+    def "is incubating when @Incubating is attached to property"() {
+        def notIncubating = new PropertyMetaData('param', classMetaData)
+        def incubating = new PropertyMetaData('param', classMetaData)
+        incubating.addAnnotationTypeName("org.gradle.api.Incubating")
+
+        expect:
+        !notIncubating.incubating
+        incubating.incubating
+    }
+}
+
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaDataTest.groovy
new file mode 100644
index 0000000..ca803b9
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/model/TypeMetaDataTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.source.model
+
+import spock.lang.Specification
+
+class TypeMetaDataTest extends Specification {
+    final TypeMetaData type = new TypeMetaData('org.gradle.SomeType')
+
+    def rawTypeForSimpleType() {
+        expect:
+        type.rawType.signature == 'org.gradle.SomeType'
+    }
+
+    def rawTypeForArrayType() {
+        type.addArrayDimension()
+        type.addArrayDimension()
+
+        expect:
+        type.rawType.signature == 'org.gradle.SomeType[][]'
+    }
+
+    def rawTypeForVarargsType() {
+        type.setVarargs()
+
+        expect:
+        type.rawType.signature == 'org.gradle.SomeType...'
+    }
+
+    def rawTypeForParameterizedArrayType() {
+        type.addArrayDimension()
+        type.addArrayDimension()
+        type.addTypeArg(new TypeMetaData('Type1'))
+
+        expect:
+        type.rawType.signature == 'org.gradle.SomeType[][]'
+    }
+
+    def rawTypeForParameterizedType() {
+        type.addTypeArg(new TypeMetaData('Type1'))
+        type.addTypeArg(new TypeMetaData('Type2'))
+
+        expect:
+        type.rawType.signature == 'org.gradle.SomeType'
+    }
+
+    def rawTypeForWildcardType() {
+        type.setWildcard()
+
+        expect:
+        type.rawType.signature == 'java.lang.Object'
+    }
+
+    def rawTypeForWildcardWithUpperBound() {
+        type.setUpperBounds(new TypeMetaData('OtherType'))
+
+        expect:
+        type.rawType.signature == 'OtherType'
+    }
+
+    def rawTypeForWildcardWithLowerBound() {
+        type.setLowerBounds(new TypeMetaData('OtherType'))
+
+        expect:
+        type.rawType.signature == 'java.lang.Object'
+    }
+
+    def formatsSignature() {
+        expect:
+        type.signature == 'org.gradle.SomeType'
+    }
+
+    def formatsSignatureForArrayType() {
+        type.addArrayDimension()
+        type.addArrayDimension()
+
+        expect:
+        type.signature == 'org.gradle.SomeType[][]'
+    }
+
+    def formatsSignatureForArrayAndVarargsType() {
+        type.addArrayDimension()
+        type.addArrayDimension()
+        type.setVarargs()
+
+        expect:
+        type.signature == 'org.gradle.SomeType[][]...'
+    }
+
+    def formatsSignatureForParameterizedType() {
+        type.addTypeArg(new TypeMetaData('Type1'))
+        type.addTypeArg(new TypeMetaData('Type2'))
+
+        expect:
+        type.signature == 'org.gradle.SomeType<Type1, Type2>'
+    }
+
+    def formatsSignatureForWildcardType() {
+        type.setWildcard()
+
+        expect:
+        type.signature == '?'
+    }
+
+    def formatsSignatureForWildcardWithUpperBound() {
+        type.setUpperBounds(new TypeMetaData('OtherType'))
+
+        expect:
+        type.signature == '? extends OtherType'
+    }
+
+    def formatsSignatureForWildcardWithLowerBound() {
+        type.setLowerBounds(new TypeMetaData('OtherType'))
+
+        expect:
+        type.signature == '? super OtherType'
+    }
+
+    def visitsSignature() {
+        TypeMetaData.SignatureVisitor visitor = Mock()
+
+        when:
+        type.visitSignature(visitor)
+
+        then:
+        1 * visitor.visitType('org.gradle.SomeType')
+        0 * visitor._
+    }
+
+    def visitsSignatureForArrayType() {
+        TypeMetaData.SignatureVisitor visitor = Mock()
+        type.addArrayDimension()
+        type.addArrayDimension()
+
+        when:
+        type.visitSignature(visitor)
+
+        then:
+        1 * visitor.visitType('org.gradle.SomeType')
+        1 * visitor.visitText('[][]')
+        0 * visitor._
+    }
+
+    def visitsSignatureForParameterizedType() {
+        TypeMetaData.SignatureVisitor visitor = Mock()
+        type.addTypeArg(new TypeMetaData('OtherType'))
+
+        when:
+        type.visitSignature(visitor)
+
+        then:
+        1 * visitor.visitType('org.gradle.SomeType')
+        1 * visitor.visitText('<')
+        1 * visitor.visitType('OtherType')
+        1 * visitor.visitText('>')
+        0 * visitor._
+    }
+
+    def visitsSignatureForWildcardType() {
+        TypeMetaData.SignatureVisitor visitor = Mock()
+        type.setWildcard()
+
+        when:
+        type.visitSignature(visitor)
+
+        then:
+        1 * visitor.visitText('?')
+        0 * visitor._
+    }
+}
diff --git a/config/checkstyle/checkstyle-groovy.xml b/config/checkstyle/checkstyle-groovy.xml
index 321abae..b07498f 100644
--- a/config/checkstyle/checkstyle-groovy.xml
+++ b/config/checkstyle/checkstyle-groovy.xml
@@ -23,4 +23,10 @@
     <module name="RegexpHeader">
         <property name="headerFile" value="${checkstyleConfigDir}/required-header.txt"/>
     </module>
+    <module name="RegexpSingleline">
+        <property name="format" value="File \| Settings \| File Templates"/>
+    </module>
+    <module name="RegexpSingleline">
+        <property name="format" value="Created with IntelliJ IDEA"/>
+    </module>
 </module>
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 6bedd42..4c39c9a 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -104,7 +104,10 @@
     </module>
     <module name="FileTabCharacter"/>
     <module name="RegexpSingleline">
-        <property name="format" value="To change body of implemented methods use File \| Settings \| File Templates"/>
+        <property name="format" value="File \| Settings \| File Templates"/>
+    </module>
+    <module name="RegexpSingleline">
+        <property name="format" value="Created with IntelliJ IDEA"/>
     </module>
 
     <!-- allows suppressing using the //CHECKSTYLE:ON //CHECKSTYLE:OFF -->
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index ad7a323..c948e65 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -11,6 +11,10 @@
     <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]plugins[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]plugins[/\\][^/\\]+"/>
     <suppress checks="JavadocPackage"
+              files=".*[/\\]subprojects[/\\]reporting[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]plugins[/\\][^/\\]+"/>
+    <suppress checks="JavadocPackage"
+              files=".*[/\\]subprojects[/\\]diagnostics[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]plugins[/\\][^/\\]+"/>
+    <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]plugins[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
     <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]scala[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
diff --git a/gradle/buildReceipt.gradle b/gradle/buildReceipt.gradle
new file mode 100644
index 0000000..9ead861
--- /dev/null
+++ b/gradle/buildReceipt.gradle
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 org.gradle.internal.os.OperatingSystem
+
+ext {
+    buildReceiptFileName = "build-receipt.properties"
+
+    readBuildReceipt = { buildReceiptFile ->
+        buildReceiptFile = file(buildReceiptFile)
+        if (!buildReceiptFile.exists()) {
+            throw new GradleException("Can't read build receipt file '$buildReceiptFile' as it doesn't exist")
+        }
+        buildReceiptFile.withInputStream {
+            def p = new Properties()
+            p.load(it)
+            p
+        }
+    }
+}
+
+task determineCommitId {
+    ext.commitId = null
+
+    doLast {
+        def strategies = []
+
+        def env = System.getenv()
+
+        // Builds of Gradle happening on the CI server
+        strategies << {
+            env["BUILD_VCS_NUMBER"]
+        }
+
+        // For the discovery builds, this points to the Gradle revision
+        strategies << {
+            env.find { it.key.startsWith("BUILD_VCS_NUMBER_Gradle_Master") }?.value
+        }
+
+        // If it's a checkout, ask Git for it
+        strategies << {
+            if (file(".git/HEAD").exists()) {
+                def baos = new ByteArrayOutputStream()
+                def execResult = exec {
+                    ignoreExitValue = true
+                    commandLine = ["git", "log", "-1", "--format=%H"]
+                    if (OperatingSystem.current().windows) {
+                        commandLine = ["cmd", "/c"] + commandLine
+                    }
+
+                    standardOutput = baos
+                }
+                if (execResult.exitValue == 0) {
+                    new String(baos.toByteArray(), "utf8").trim()
+                } else {
+                    // Read commit id directly from filesystem
+                    def headRef = file(".git/HEAD").text
+                    headRef = headRef.replaceAll('ref: ', '').trim()
+                    file(".git/$headRef").text.trim()
+                }
+            } else {
+                null
+            }
+        }
+
+        // It's a source distribution, we don't know.
+        strategies << {
+            if (!file("design-docs").directory) {
+                "<unknown>"
+            }
+        }
+
+        for (strategy in strategies) {
+            commitId = strategy()
+            if (commitId) {
+                break
+            }
+        }
+        if (!commitId) {
+            throw new InvalidUserDataException("Could not determine commit id")
+        }
+    }
+}
+
+task createBuildReceipt(dependsOn: determineCommitId) {
+    ext.receiptFile = file("$buildDir/$buildReceiptFileName")
+    outputs.file receiptFile
+    outputs.upToDateWhen { false }
+    doLast {
+        def hostName
+        try {
+            hostName = InetAddress.localHost.hostName
+        } catch (UnknownHostException e) {
+            hostName = "unknown"
+        }
+        def data = [
+                commitId:  determineCommitId.commitId,
+                versionNumber: version,
+                buildTimestamp: buildTimestamp,
+                username: System.properties["user.name"],
+                hostname: hostName,
+                javaVersion: System.properties["java.version"],
+                osName: System.properties["os.name"],
+                osVersion: System.properties["os.version"]
+        ]
+
+        receiptFile.parentFile.mkdirs()
+
+        // We write this out ourself instead of using the properties class to avoid the
+        // annoying timestamp that insists on placing in there, that throws out incremental.
+        def content = data.entrySet().collect { "$it.key=$it.value" }.sort().join("\n")
+        receiptFile.setText(content, "ISO-8859-1")
+    }
+}
diff --git a/gradle/classycle.gradle b/gradle/classycle.gradle
index 7264cd1..16685d2 100644
--- a/gradle/classycle.gradle
+++ b/gradle/classycle.gradle
@@ -1,37 +1,44 @@
+allprojects {
+    ext.useClassycle = {
+        configurations {
+            classycle
+        }
 
-configurations {
-    classycle
-}
+        dependencies {
+            classycle 'classycle:classycle:1.4 at jar'
+        }
 
-dependencies {
-    classycle 'classycle:classycle:1.4 at jar'
-}
+        task classycle
 
-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()
-            try {
-                ant.classycleDependencyCheck(reportFile: reportFile, failOnUnwantedDependencies: true, mergeInnerClasses: true,
-                    """
+        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()
+                    try {
+                        ant.classycleDependencyCheck(reportFile: reportFile, failOnUnwantedDependencies: true, mergeInnerClasses: true,
+                                """
+                        show allResults
                         check absenceOfPackageCycles > 1 in org.gradle.*
                     """
-                ) {
-                    fileset(dir: sourceSet.output.classesDir)
+                        ) {
+                            fileset(dir: sourceSet.output.classesDir)
+                        }
+                    } catch(Exception e) {
+                        throw new RuntimeException("Classycle check failed: $e.message. See report at ${new org.gradle.logging.ConsoleRenderer().asClickableFileUrl(reportFile)}", e)
+                    }
                 }
-            } catch(Exception e) {
-                throw new RuntimeException("Classycle check failed: $e.message. See report at $reportFile", e)
             }
+            classycle.dependsOn taskName
+            check.dependsOn taskName
+            codeQuality.dependsOn taskName
         }
     }
-    check.dependsOn taskName
-    codeQuality.dependsOn taskName
 }
diff --git a/gradle/compile.gradle b/gradle/compile.gradle
index ce2a6ed..766abd9 100644
--- a/gradle/compile.gradle
+++ b/gradle/compile.gradle
@@ -1,7 +1,9 @@
 tasks.withType(Compile) {
     options.encoding = 'utf-8'
+    options.compilerArgs = ['-Xlint:-options']
 }
 tasks.withType(GroovyCompile) {
     options.encoding = 'utf-8'
+    options.compilerArgs = ['-Xlint:-options']
     groovyOptions.encoding = 'utf-8'
 }
diff --git a/gradle/conventions-dsl.gradle b/gradle/conventions-dsl.gradle
deleted file mode 100644
index d14950a..0000000
--- a/gradle/conventions-dsl.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-    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/dependencies.gradle b/gradle/dependencies.gradle
new file mode 100644
index 0000000..b6ef1fe
--- /dev/null
+++ b/gradle/dependencies.gradle
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ext {
+    versions = [:]
+    libraries = [:]
+}
+
+versions.commons_io = 'commons-io:commons-io:1.4'
+
+libraries.ant = dependencies.module('org.apache.ant:ant:1.8.4') {
+    dependency 'org.apache.ant:ant-launcher:1.8.4 at jar'
+}
+
+libraries.asm =  'org.ow2.asm:asm-all:4.0 at jar'
+libraries.commons_cli = 'commons-cli:commons-cli:1.2 at jar'
+libraries.commons_io = dependencies.module(versions.commons_io)
+libraries.commons_lang = 'commons-lang:commons-lang:2.6 at jar'
+libraries.commons_collections = 'commons-collections:commons-collections:3.2.1 at jar'
+libraries.ivy = dependencies.module('org.apache.ivy:ivy:2.2.0'){
+    dependency "com.jcraft:jsch:0.1.46"
+}
+libraries.jcip = "net.jcip:jcip-annotations:1.0 at jar"
+libraries.inject = dependencies.module('javax.inject:javax.inject:1')
+
+// Logging
+libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.6 at jar'
+libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.6') {
+    dependency libraries.slf4j_api
+}
+libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.6') {
+    dependency libraries.slf4j_api
+}
+libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.6') {
+    dependency libraries.slf4j_api
+}
+libraries.logback_core = 'ch.qos.logback:logback-core:1.0.6 at jar'
+libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.6') {
+    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.2.1') {
+    dependency "org.apache.httpcomponents:httpcore:4.2.1 at jar"
+    dependency libraries.jcl_to_slf4j
+    dependency "commons-codec:commons-codec:1.6 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.4 at jar',
+        ant_antlr: 'org.apache.ant:ant-antlr:1.8.4 at jar',
+        antlr: 'antlr:antlr:2.7.7 at jar',
+        dom4j: 'dom4j:dom4j:1.6.1 at jar',
+        guava: 'com.google.guava:guava:11.0.2 at jar',
+        jsr305: 'com.google.code.findbugs:jsr305:1.3.9',
+        groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
+        jaxen: 'jaxen:jaxen:1.1 at jar',
+        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',
+        xbean: 'org.apache.xbean:xbean-reflect:3.4 at jar', //required by maven3 classes
+        nativePlatform: 'net.rubygrapefruit:native-platform:0.2'
+]
+
+libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
+    dependency "org.apache.maven:maven-settings:3.0.4 at jar"
+    dependency "org.apache.maven:maven-settings-builder:3.0.4 at jar"
+
+    //plexus:
+    dependency "org.codehaus.plexus:plexus-utils:2.0.6 at jar"
+    dependency "org.codehaus.plexus:plexus-interpolation:1.14 at jar"
+    dependency "org.codehaus.plexus:plexus-component-annotations:1.5.5 at jar"
+    dependency "org.codehaus.plexus:plexus-container-default:1.5.5 at jar"
+    dependency "org.codehaus.plexus:plexus-classworlds:2.4 at jar"
+
+    //sonatype plexus
+    dependency "org.sonatype.plexus:plexus-cipher:1.7 at jar"
+    dependency "org.sonatype.plexus:plexus-sec-dispatcher:1.3 at jar"
+
+    //core:
+    dependency "org.apache.maven:maven-core:3.0.4 at jar"
+    dependency "org.apache.maven:maven-model-builder:3.0.4 at jar"
+    dependency "org.apache.maven:maven-model:3.0.4 at jar"
+
+    //somewhat core:
+    dependency "org.apache.maven:maven-artifact:3.0.4 at jar"
+    dependency "org.apache.maven:maven-compat:3.0.4 at jar"
+    dependency "org.apache.maven:maven-repository-metadata:3.0.4 at jar"
+    dependency "org.apache.maven:maven-plugin-api:3.0.4 at jar"
+    dependency "org.apache.maven:maven-aether-provider:3.0.4 at jar"
+    dependency "org.apache.maven.wagon:wagon-provider-api:2.2 at jar"
+
+    //eather:
+    dependency "org.sonatype.aether:aether-api:1.13.1 at jar"
+    dependency "org.sonatype.aether:aether-impl:1.13.1 at jar"
+    dependency "org.sonatype.aether:aether-spi:1.13.1 at jar"
+    dependency "org.sonatype.aether:aether-util:1.13.1 at jar"
+}
+
+libraries.spock = [
+    'org.spockframework:spock-core:0.7-groovy-1.8 at jar',
+    libraries.groovy,
+    'org.objenesis:objenesis:1.2',
+    'cglib:cglib-nodep:2.2.2'
+]
+libraries.jmock = [
+    'org.jmock:jmock:2.5.1',
+    'org.hamcrest:hamcrest-core:1.1',
+    'org.hamcrest:hamcrest-library:1.1',
+    dependencies.create('org.jmock:jmock-junit4:2.5.1') { exclude group: 'junit', module: 'junit-dep' }, //junit-dep pulls old definitions of core junit types.
+    'org.jmock:jmock-legacy:2.5.1',
+    'org.objenesis:objenesis:1.2',
+    'cglib:cglib-nodep:2.2'
+]
diff --git a/gradle/eclipse.gradle b/gradle/eclipse.gradle
index c9762f1..7cd85cc 100644
--- a/gradle/eclipse.gradle
+++ b/gradle/eclipse.gradle
@@ -1,16 +1,16 @@
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
-
-allprojects {
+allprojects { project->
 	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') }
+    plugins.withType(JavaPlugin) {
+        eclipse {
+            classpath {
+                file.whenMerged { classpath ->
+                    //There are classes in here not designed to be compiled, but just used in our testing
+                    classpath.entries.removeAll { it.path.contains('src/integTest/resources') }
+                    //Workaround for some projects referring to themselves as dependent projects
+                    classpath.entries.removeAll { it.path.contains("$project.name") && it.kind=='src' }
+                    classpath.entries.removeAll { it.path.contains("$project.name/build") && it.kind=='lib' }
+                }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
index 2368dcb..76d6952 100644
--- a/gradle/groovyProject.gradle
+++ b/gradle/groovyProject.gradle
@@ -4,38 +4,67 @@ 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 = []
-    }
+sourceCompatibility = 1.5
+targetCompatibility = 1.5
+
+ext {
+    hasGroovySource = sourceSets.main.groovy.srcDirs.any { it.exists() }
+    compileTasks = tasks.matching { it instanceof Compile || it instanceof GroovyCompile }
+    testTasks = tasks.withType(Test)
+    generatedResourcesDir = file("$buildDir/generated-resources/main")
+    generatedTestResourcesDir = file("$buildDir/generated-resources/test")
+    jarTasks = tasks.withType(Jar)
 }
 
 dependencies {
     testCompile libraries.junit, libraries.jmock, libraries.spock
 }
 
+if (!hasGroovySource) {
+    // Remove Groovy configuration from compile classpath
+    configurations.compile.extendsFrom = []
+}
+
+// Extracted as it's also used by buildSrc
 apply from: "$rootDir/gradle/compile.gradle"
 
-test {
-    maxParallelForks = guessMaxForks(project)
+task classpathManifest(type: ClasspathManifest)
+
+sourceSets {
+    main.output.dir generatedResourcesDir, builtBy: classpathManifest
 }
 
-tasks.matching { it instanceof Compile || it instanceof GroovyCompile }.all {
-    options.useAnt = false
+compileTasks.all { options.useAnt = false }
+testTasks.all { task ->
+    maxParallelForks = rootProject.maxParallelForks
+    if (isCiServer) {
+        doFirst {
+            println "maxParallelForks for '$task.path' is $task.maxParallelForks"
+        }
+    }
 }
 
-tasks.withType(Jar).each { jar ->
-    jar.manifest.mainAttributes([
-            (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
-            (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
-    ])
+jarTasks.all { jar ->
+    jar.manifest.mainAttributes(
+        (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
+        (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
+    )
 }
 
-ext.generatedResourcesDir = file("$buildDir/generated-resources/main")
+// 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"
 
-task classpathManifest(type: ClasspathManifest)
-sourceSets.main.output.dir generatedResourcesDir, builtBy: classpathManifest
+    dependencies {
+        add(compileConfiguration, project(path: projectPath, configuration: "testFixturesUsageCompile"))
+        add(compileConfiguration, project(':internalTesting'))
+        add(runtimeConfiguration, project(path: projectPath, configuration: "testFixturesUsageRuntime"))
+    }
+}
 
 if (file("src/testFixtures").exists()) {
     apply from: "$rootDir/gradle/testFixtures.gradle"
@@ -46,6 +75,9 @@ if (file("src/integTest").exists()) {
 }
 
 class ClasspathManifest extends DefaultTask {
+
+    FileCollection input = project.configurations.runtime
+
     @OutputFile
     File getManifestFile() {
         return new File(project.generatedResourcesDir, "${project.archivesBaseName}-classpath.properties")
@@ -54,10 +86,10 @@ class ClasspathManifest extends DefaultTask {
     @Input
     Properties getProperties() {
         def properties = new Properties()
-        properties.runtime = project.configurations.runtime.fileCollection {
+        properties.runtime = input.fileCollection {
             (it instanceof ExternalDependency) || (it instanceof FileCollectionDependency)
         }.collect {it.name}.join(',')
-        properties.projects = project.configurations.runtime.allDependencies.withType(ProjectDependency).collect {it.dependencyProject.archivesBaseName}.join(',')
+        properties.projects = input.allDependencies.withType(ProjectDependency).collect {it.dependencyProject.archivesBaseName}.join(',')
         return properties
     }
 
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
index ebc7e74..906ca03 100644
--- a/gradle/idea.gradle
+++ b/gradle/idea.gradle
@@ -4,17 +4,17 @@ 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 })))
+subprojects {
+    plugins.withType(JavaPlugin) {
+        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')
@@ -28,7 +28,6 @@ idea {
         languageLevel = '1.5'
 
         ipr {
-
             withXml { provider ->
                 // Exclude resource directories from compilation and add them back in as classpath resources
                 def node = provider.asNode()
@@ -38,7 +37,7 @@ idea {
                     compilerConfig.remove(exclude)
                 }
                 exclude = compilerConfig.appendNode('excludeFromCompile')
-                Collection resourceFolder = groovyProjects().collect { project -> project.sourceSets*.resources*.srcDirs }.flatten()
+                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])
@@ -55,12 +54,12 @@ idea {
                         }
                     }
                 }
-                Collection sourceDirs = groovyProjects().collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
+                Collection sourceDirs = groovyProjects.collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
                 sourceDirs.each { sourceFolder ->
-                    if (!Jvm.current().isJava7()) {
+                    if (!javaVersion.java7) {
                         excludeSource("jdk7", sourceFolder)
                     }
-                    if (!Jvm.current().isJava6Compatible()) {
+                    if (!javaVersion.java6Compatible) {
                         excludeSource("jdk6", sourceFolder)
                     }
                 }
@@ -211,7 +210,7 @@ idea {
 
         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"
+        vmParameters.'@value' = "\"-DintegTest.gradleHomeDir=${intTestImage.destinationDir}\" -ea -Dorg.gradle.integtest.executer=embedded -XX:MaxPermSize=512m -Xmx512m"
 
         // Add an application configuration
         runManagerConfig.'@selected' = 'Application.Gradle'
@@ -268,7 +267,9 @@ idea {
                   <option name='PASS_PARENT_ENVS' value='true' />
                   <module name='${runnerClassModule}' />
                   <envs />
-                  <method />
+                  <method>
+                    <option name="Make" enabled="false" />
+                  </method>
                 </configuration>
             """))
 
diff --git a/gradle/incomingDistributions.gradle b/gradle/incomingDistributions.gradle
new file mode 100644
index 0000000..38a1667
--- /dev/null
+++ b/gradle/incomingDistributions.gradle
@@ -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.
+ */
+
+// depends on buildReceipt.gradle
+
+ext {
+    incomingAllDistribution = null
+    incomingDistributionsBuildReceipt = null
+}
+
+if (useIncomingDistributions) {
+    def incomingDistributionsDir = file("incoming-distributions")
+    if (!incomingDistributionsDir.directory) {
+        throw new GradleException("useIncomingDistributions set, but 'incoming-distributions' is not an existing directory")
+    }
+
+    incomingDistributionsBuildReceipt = readBuildReceipt(file("$incomingDistributionsDir/build-receipt.properties"))
+
+    def contents = incomingDistributionsDir.listFiles()
+    incomingAllDistribution = file("$incomingDistributionsDir/gradle-$incomingDistributionsBuildReceipt.versionNumber-all.zip")
+    if (!incomingAllDistribution.exists()) {
+        throw new GradleException("Couldn't find all distribution in incoming distributions (contents: $contents.name)")
+    }
+}
diff --git a/gradle/intTestImage.gradle b/gradle/intTestImage.gradle
new file mode 100644
index 0000000..bfeb9c1
--- /dev/null
+++ b/gradle/intTestImage.gradle
@@ -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.
+ */
+
+evaluationDependsOn ":distributions"
+evaluationDependsOn ":docs"
+
+task intTestImage(type: Sync) {
+    into file("$buildDir/integ test")
+}
+
+if (useIncomingDistributions) {
+    task unpackIncomingAllDistribution(type: Sync) {
+        from zipTree(incomingAllDistribution)
+        into "$buildDir/tmp/unpacked-incoming-all-distribution"
+    }
+
+    // Compensate for the top level dir in the zip
+    def unpackedPath = "$unpackIncomingAllDistribution.destinationDir/${incomingAllDistribution.name - "-all.zip"}"
+
+    intTestImage {
+        dependsOn unpackIncomingAllDistribution
+        from unpackedPath
+    }
+} else {
+    intTestImage {
+        with project(":distributions").binDistImage
+        into "samples", {
+            from { project(":docs").samples }
+        }
+        doLast { task ->
+            ant.chmod(dir: "$destinationDir/bin", perm: "ugo+rx", includes: "**/*")
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index dfa03dd..aa51234 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -1,10 +1,4 @@
-/*
- * Adds an 'integTest' source set, which contains the integration tests for the project.
- */
-import org.gradle.build.integtest.IntegTestPlugin
-
 apply plugin: 'java'
-rootProject.apply plugin: IntegTestPlugin
 
 sourceSets {
     integTest {
@@ -20,82 +14,80 @@ configurations {
 
 dependencies {
     integTestCompile project(":internalIntegTesting")
-}
 
-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)
-        }
-    }
-}
-
-plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) { // lazy as plugin not applied yet
-    eclipse {
-        classpath {
-            plusConfigurations.add(configurations.integTestCompile)
-            plusConfigurations.add(configurations.integTestRuntime)
-        }
-    }
+    //so that implicit help tasks are available:
+    integTestRuntime project(':diagnostics')
+    //above can be removed when we implement the auto-apply plugins
 }
 
 ext.integTestTasks = tasks.withType(Test).matching { it.name.toLowerCase().endsWith('integtest') }
 
-integTestTasks.all {
-    dependsOn ':intTestImage', project(":docs").tasks.matching { it.name == "samples" } // lazy as doc not eval'd yet
+integTestTasks.all { Test task ->
+    dependsOn ':intTestImage'
     testClassesDir = sourceSets.integTest.output.classesDir
     classpath = sourceSets.integTest.runtimeClasspath
     testSrcDirs = []
     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*'
+        forkEvery = 1
     }
 
-    doFirst {
-        testReportDir = file("${project.reporting.baseDir}/$name")
-        systemProperties['integTest.gradleHomeDir'] = integTestImageDir.absolutePath
-        systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
-        systemProperties['integTest.samplesdir'] = project(":docs").samplesDir.absolutePath
-        systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
-        systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
+    dependsOn project.task("configure${task.name.capitalize()}") << {
+        configure(task) {
+            testReportDir = file("${project.reporting.baseDir}/$name")
+            systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.destinationDir.absolutePath
+            systemProperties['integTest.gradleUserHomeDir'] = rootProject.file('intTestHomeDir').absolutePath
+            systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
+            
+            // If the projects int test need the distributions, they should add:
+            // inputs.files rootProject.buildDists
+            systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
+
+            // The user home dir is not wiped out by clean
+            // Move the daemon working space underneath the build dir so they don't pile up on CI
+            systemProperties['org.gradle.integtest.daemon.registry'] = file("$rootProject.buildDir/daemon").absolutePath
+        }
     }
 }
 
-['embedded', 'forking', 'daemon', 'embeddedDaemon'].each {
-    def mode = it
-    def taskName = "${it}IntegTest"
+task integTest(type: Test) {
+    def defaultExecuter = project.hasProperty("defaultIntegTestExecuter") ? project.defaultIntegTestExecuter : "embedded"
+    systemProperties['org.gradle.integtest.executer'] = defaultExecuter
+}
+check.dependsOn(integTest)
+
+['embedded', 'forking', 'daemon', 'embeddedDaemon', 'parallel'].each { mode ->
+    def taskName = "${mode}IntegTest"
     tasks.addRule(taskName) { name ->
-        if (name != taskName) { return }
-        tasks.add(taskName, Test).configure {
-            systemProperties['org.gradle.integtest.executer'] = mode
+        if (name == taskName) { 
+            tasks.add(taskName, Test).configure {
+                systemProperties['org.gradle.integtest.executer'] = mode
+            }
         }
     }
 }
 
-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);
+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)
+        }
+    }
 }
 
-task integTest(type: Test) {
-    doFirst {
-        systemProperties['org.gradle.integtest.executer'] = integTestMode
+plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) { // lazy as plugin not applied yet
+    eclipse {
+        classpath {
+            plusConfigurations.add(configurations.integTestCompile)
+            plusConfigurations.add(configurations.integTestRuntime)
+        }
     }
 }
-
-tasks.findByName("check")?.dependsOn(integTest)
-
-
diff --git a/gradle/noDependencyResolutionDuringConfiguration.gradle b/gradle/noDependencyResolutionDuringConfiguration.gradle
new file mode 100644
index 0000000..525593f
--- /dev/null
+++ b/gradle/noDependencyResolutionDuringConfiguration.gradle
@@ -0,0 +1,14 @@
+def afterEvaluation = false
+gradle.projectsEvaluated {
+    afterEvaluation = true
+}
+
+allprojects { project ->
+    configurations.all { configuration ->
+        configuration.incoming.beforeResolve {
+           if (!afterEvaluation) {
+               throw new Exception("Configuration $configuration.name of project $project.name is being resolved at configuration time.")
+           }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index b906c2b..e6265f2 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -45,7 +45,15 @@ artifacts {
     publishRuntime new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(archivesBaseName, 'pom', 'pom', null, new Date(), generatePom.pomFile, generatePom)
 }
 
-uploadArchives {
+uploadArchives { task ->
+    gradle.taskGraph.whenReady { graph ->
+        if (graph.hasTask(task)) {
+            // check properties defined and fail early
+            artifactoryUserName
+            artifactoryUserPassword
+        }
+    }
+
     configuration = configurations.publishRuntime
     dependsOn generatePom
     uploadDescriptor = false
diff --git a/gradle/testWithUnknownOS.gradle b/gradle/testWithUnknownOS.gradle
index 7c67676..4d07ff4 100644
--- a/gradle/testWithUnknownOS.gradle
+++ b/gradle/testWithUnknownOS.gradle
@@ -1,5 +1,6 @@
 if (project.hasProperty("testWithUnknownOS")) {
     tasks.withType(Test) {
+        systemProperty "org.gradle.integtest.unknownos", "true"
         systemProperty "os.arch", "unknown architecture"
         systemProperty "os.name", "unknown operating system"
         systemProperty "os.version", "unknown version"
diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle
index 571c1fe..3f68ddd 100644
--- a/gradle/versioning.gradle
+++ b/gradle/versioning.gradle
@@ -1,6 +1,17 @@
 def timestampFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmssZ')
 timestampFormat.timeZone = TimeZone.getTimeZone("UTC")
 
+if (incomingDistributionsBuildReceipt) {
+    def incomingVersion = incomingDistributionsBuildReceipt.versionNumber
+    def match = incomingVersion =~ /(.+)-(\d{14}[+-]\d{4})/
+    if (!match) {
+        throw new GradleException("Can't extract version number from incoming distribution build receipt version number: $incomingVersion")
+    }
+
+    version = match[0][1]
+    ext.buildTimestamp = match[0][2]
+}
+
 if (project.hasProperty("buildTimestamp")) {
     ext.buildTime = timestampFormat.parse(buildTimestamp)
 } else {
@@ -41,8 +52,8 @@ if (version == "unspecified") {
     // version = "0.0"
 
     // Using 0.0 causes issues with the Tooling API. It's something to do with older versions of Gradle not
-    // understanding the 0.0 version number scheme. For the time being, we are going to hardcode 1.1.
-    version = "1.1"
+    // understanding the 0.0 version number scheme. For the time being, we are going to hardcode the version.
+    version = "1.3"
 
     ext.isSymbolicVersion = false
 } else {
diff --git a/gradle/wrapper.gradle b/gradle/wrapper.gradle
new file mode 100644
index 0000000..5fd0747
--- /dev/null
+++ b/gradle/wrapper.gradle
@@ -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.
+ */
+
+def wrapperUpdateTask = { name, label ->
+    task "${name}Wrapper"(type: Wrapper) {
+        group = "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"
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7723e1e..ec6b49c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu May 17 15:08:14 BST 2012
+#Sun Sep 02 13:16:06 EST 2012
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.1-20120723222958+0000-bin.zip
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.3-20121109123943+0000-bin.zip
diff --git a/gradlew b/gradlew
index bafc554..39e1569 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 ##############################################################################
 ##
@@ -101,7 +101,7 @@ if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; 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"
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
     fi
 fi
 
diff --git a/settings.gradle b/settings.gradle
index bf63d06..801a90d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+include 'distributions'
 include 'baseServices'
+include 'baseServicesGroovy'
 include 'core'
 include 'coreImpl'
 include 'wrapper'
@@ -43,7 +45,11 @@ include 'internalTesting'
 include 'internalIntegTesting'
 include 'performance'
 include 'javascript'
-include 'migration'
+include 'buildComparison'
+include 'reporting'
+include 'diagnostics'
+include 'publish'
+include 'ivy'
 
 rootProject.name = 'gradle'
 rootProject.children.each {project ->
diff --git a/subprojects/announce/announce.gradle b/subprojects/announce/announce.gradle
index 7c52134..68a1e47 100644
--- a/subprojects/announce/announce.gradle
+++ b/subprojects/announce/announce.gradle
@@ -18,10 +18,10 @@ dependencies {
 
     compile libraries.slf4j_api
     compile project(':core')
-    compile project(':plugins')
+    integTestRuntime project(':plugins')
 }
 
-if (!Jvm.current().java6Compatible) {
+if (!javaVersion.java6Compatible) {
     sourceSets.main.groovy.exclude '**/jdk6/**'
 }
 
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
deleted file mode 100644
index 3ffe1a2..0000000
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.groovy
+++ /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.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/AnnouncerUnavailableException.java b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.java
new file mode 100644
index 0000000..049e768
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.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.plugins.announce.internal;
+
+/**
+ * Thrown when the target announcer is not available.
+ */
+public class AnnouncerUnavailableException extends RuntimeException {
+    public AnnouncerUnavailableException(String message) {
+        super(message);
+    }
+
+    public AnnouncerUnavailableException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/base-services-groovy/base-services-groovy.gradle b/subprojects/base-services-groovy/base-services-groovy.gradle
new file mode 100644
index 0000000..90d6a16
--- /dev/null
+++ b/subprojects/base-services-groovy/base-services-groovy.gradle
@@ -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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+    compile project(":baseServices")
+}
+
+useTestFixtures()
+useClassycle()
+
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/InvalidActionClosureException.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/InvalidActionClosureException.java
new file mode 100644
index 0000000..fc82db7
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/InvalidActionClosureException.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.CollectionUtils;
+
+import java.util.List;
+
+/**
+ * Thrown when a {@link Closure} is given as an {@link Action} implementation, but has the wrong signature.
+ */
+public class InvalidActionClosureException extends GradleException {
+
+    private final Closure<?> closure;
+    private final Object argument;
+
+    public InvalidActionClosureException(Closure<?> closure, Object argument) {
+        super(toMessage(closure, argument));
+        this.closure = closure;
+        this.argument = argument;
+    }
+
+    private static String toMessage(Closure<?> closure, Object argument) {
+        List<Object> classNames = CollectionUtils.collect(closure.getParameterTypes(), new Transformer<Object, Class>() {
+            public Object transform(Class clazz) {
+                return clazz.getName();
+            }
+        });
+        return String.format(
+                "The closure '%s' is not valid as an action for argument '%s'. It should accept no parameters, or one compatible with type '%s'. It accepts (%s).",
+                closure, argument, argument.getClass().getName(), CollectionUtils.join(", ", classNames)
+        );
+    }
+
+    /**
+     * The closure being used as an action.
+     *
+     * @return The closure being used as an action.
+     */
+    public Closure<?> getClosure() {
+        return closure;
+    }
+
+    /**
+     * The argument the action was executed with.
+     *
+     * @return The argument the action was executed with.
+     */
+    public Object getArgument() {
+        return argument;
+    }
+}
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java
new file mode 100644
index 0000000..5b78f5e
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * <p><b>Start Here:</b> Gradle's {@link org.gradle.api.Project} API, which is available from your build files. The
+ * API used from your build files is made up of 2 main interfaces:</p>
+ *
+ * <ul>
+ * <li>{@link org.gradle.api.Project}</li>
+ * <li>{@link org.gradle.api.Task}</li>
+ * </ul>
+ */
+package org.gradle.api;
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/AndSpec.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/AndSpec.java
new file mode 100644
index 0000000..7da1f73
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/AndSpec.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.specs;
+
+import groovy.lang.Closure;
+import org.gradle.api.specs.internal.ClosureSpec;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A {@link org.gradle.api.specs.CompositeSpec} which requires all its specs to be true in order to evaluate to true.
+ * Uses lazy evaluation.
+ *
+ * @author Hans Dockter
+ * @param <T> The target type for this Spec
+ */
+public class AndSpec<T> extends CompositeSpec<T> {
+    public AndSpec(Spec<? super T>... specs) {
+        super(specs);
+    }
+
+    public AndSpec(Iterable<? extends Spec<? super T>> specs) {
+        super(specs);
+    }
+
+    public boolean isSatisfiedBy(T object) {
+        for (Spec<? super T> spec : getSpecs()) {
+            if (!spec.isSatisfiedBy(object)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public AndSpec<T> and(Spec<? super T>... specs) {
+        List<Spec<? super T>> specs1 = getSpecs();
+        List<Spec<? super T>> specs2 = Arrays.asList(specs);
+        List<Spec<? super T>> combined = new ArrayList<Spec<? super T>>(specs1.size() + specs2.size());
+        combined.addAll(specs1);
+        combined.addAll(specs2);
+        return new AndSpec<T>(combined);
+    }
+
+    public AndSpec<T> and(Closure spec) {
+        return and(new ClosureSpec<T>(spec));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java
rename to subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java
diff --git a/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.java b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.java
new file mode 100644
index 0000000..a5cc802
--- /dev/null
+++ b/subprojects/base-services-groovy/src/main/groovy/org/gradle/api/specs/package-info.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.
+ */
+
+/**
+ * Classes for defining general purpose criteria.
+ */
+package org.gradle.api.specs;
\ No newline at end of file
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
new file mode 100644
index 0000000..7db2ee9
--- /dev/null
+++ b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/internal/ClosureBackedActionTest.groovy
@@ -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.api.internal
+
+import org.gradle.api.Action
+import org.gradle.api.InvalidActionClosureException
+import spock.lang.Specification
+
+class ClosureBackedActionTest extends Specification {
+
+    def "one arg closure is called"() {
+        given:
+        def called = false
+        def thing = "1"
+        def closure = {
+            called = true
+            assert it.is(thing)
+            assert delegate.is(thing)
+        }
+
+        when:
+        action(closure).execute(thing)
+
+        then:
+        called
+    }
+
+    def "zero arg closure is called"() {
+        given:
+        def called = false
+        def thing = "1"
+        def closure = { ->
+            called = true
+            assert delegate.is(thing)
+        }
+
+        when:
+        action(closure).execute(thing)
+
+        then:
+        called
+    }
+
+    def "closure with wrong param type is given"() {
+        given:
+        def closure = { Map m -> }
+        def arg = "1"
+
+        when:
+        action(closure).execute(arg)
+
+        then:
+        def e = thrown InvalidActionClosureException
+        e.closure.is(closure)
+        e.argument.is(arg)
+        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map)."
+    }
+
+    def "closure with more than one param type is given"() {
+        given:
+        def closure = { Map m, List l -> }
+        def arg = "1"
+
+        when:
+        action(closure).execute(arg)
+
+        then:
+        def e = thrown InvalidActionClosureException
+        e.closure.is(closure)
+        e.argument.is(arg)
+        e.message == "The closure '${closure.toString()}' is not valid as an action for argument '1'. It should accept no parameters, or one compatible with type 'java.lang.String'. It accepts (java.util.Map, java.util.List)."
+    }
+
+    Action<?> action(Closure<?> c) {
+        new ClosureBackedAction(c)
+    }
+}
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java
new file mode 100644
index 0000000..b21d85c
--- /dev/null
+++ b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.specs;
+
+import org.gradle.util.CollectionUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+
+abstract public class AbstractCompositeSpecTest {
+    private Spec spec1;
+    private Spec spec2;
+
+    public abstract org.gradle.api.specs.CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs);
+
+    @Before
+    public void setUp() {
+        spec1 = new Spec<Object>() {
+            public boolean isSatisfiedBy(Object o) {
+                return false;
+            }
+        };
+        spec2 = new Spec<Object>() {
+            public boolean isSatisfiedBy(Object o) {
+                return false;
+            }
+        };
+    }
+
+    @Test
+    public void init() {
+        org.gradle.api.specs.CompositeSpec<Object> compositeSpec = createCompositeSpec(spec1, spec2);
+        Assert.assertEquals(CollectionUtils.flattenToList(spec1, spec2), compositeSpec.getSpecs());
+    }
+
+    protected Spec<Object>[] createAtomicElements(boolean... satisfies) {
+        List<Spec<Object>> result = new ArrayList<Spec<Object>>();
+        for (final boolean satisfy : satisfies) {
+            result.add(new Spec<Object>() {
+                public boolean isSatisfiedBy(Object o) {
+                    return satisfy;
+                }
+            });
+        }
+        return result.toArray(new Spec[result.size()]);
+    }
+
+    @Test
+    public void equality() {
+        assert createCompositeSpec(spec1).equals(createCompositeSpec(spec1));
+        assertFalse(createCompositeSpec(spec1).equals(createCompositeSpec(spec2)));
+    }
+}
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AndSpecTest.java b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
new file mode 100644
index 0000000..a26dda9
--- /dev/null
+++ b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.gradle.util.HelperUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AndSpecTest extends AbstractCompositeSpecTest {
+    public org.gradle.api.specs.CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs) {
+        return new AndSpec<Object>(specs);
+    }
+
+    @Test
+    public void isSatisfiedWhenNoSpecs() {
+        assertTrue(new AndSpec<Object>().isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void isSatisfiedByWithAllTrue() {
+        assertTrue(new AndSpec<Object>(createAtomicElements(true, true, true)).isSatisfiedBy(new Object()));
+    }
+
+    @Test
+    public void isSatisfiedByWithOneFalse() {
+        assertFalse(new AndSpec<Object>(createAtomicElements(true, false, true)).isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void canAddSpecs() {
+        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
+        spec = spec.and(createAtomicElements(false));
+        assertFalse(spec.isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void canAddClosureAsASpec() {
+        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
+        spec = spec.and(HelperUtil.toClosure("{ false }"));
+        assertFalse(spec.isSatisfiedBy(new Object()));
+    }
+}
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/NotSpecTest.java b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
new file mode 100644
index 0000000..4f14376
--- /dev/null
+++ b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
@@ -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.api.specs;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class NotSpecTest {
+    @Test
+    public void testIsSatisfiedBy() {
+        assertThat(new org.gradle.api.specs.NotSpec(createFilterSpec()).isSatisfiedBy(true), equalTo(false));
+        assertThat(new org.gradle.api.specs.NotSpec(createFilterSpec()).isSatisfiedBy(false), equalTo(true));
+    }
+
+    private Spec<Boolean> createFilterSpec() {
+        return new Spec<Boolean>() {
+            public boolean isSatisfiedBy(Boolean element) {
+                return element;
+            }
+        };
+    }
+}
diff --git a/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/OrSpecTest.java b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/OrSpecTest.java
new file mode 100644
index 0000000..29bd8a8
--- /dev/null
+++ b/subprojects/base-services-groovy/src/test/groovy/org/gradle/api/specs/OrSpecTest.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.api.specs;
+
+import org.gradle.api.artifacts.Dependency;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class OrSpecTest extends AbstractCompositeSpecTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    public org.gradle.api.specs.CompositeSpec createCompositeSpec(Spec... specs) {
+        return new org.gradle.api.specs.OrSpec(specs);
+    }
+
+    @Test
+    public void isSatisfiedWhenNoSpecs() {
+        assertTrue(new org.gradle.api.specs.OrSpec().isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void isSatisfiedByWithOneTrue() {
+        assertTrue(new OrSpec(createAtomicElements(false, true, false)).isSatisfiedBy(context.mock(Dependency.class)));
+    }
+
+    @Test
+    public void isSatisfiedByWithAllFalse() {
+        assertFalse(new AndSpec(createAtomicElements(false, false, false)).isSatisfiedBy(context.mock(Dependency.class)));
+    }
+}
diff --git a/subprojects/base-services/base-services.gradle b/subprojects/base-services/base-services.gradle
index 10a94af..f825ab8 100644
--- a/subprojects/base-services/base-services.gradle
+++ b/subprojects/base-services/base-services.gradle
@@ -4,8 +4,6 @@
  * 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
 
@@ -13,3 +11,4 @@ dependencies {
 }
 
 useTestFixtures()
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java b/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java
deleted file mode 100644
index 7e0a2a4..0000000
--- a/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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 a feature is experimental. This means that the feature is currently a work-in-progress and may
- * change at any time.
- */
- at Documented
- at Retention(RetentionPolicy.RUNTIME)
-public @interface Experimental {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/GradleException.java b/subprojects/base-services/src/main/java/org/gradle/api/GradleException.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/GradleException.java
rename to subprojects/base-services/src/main/java/org/gradle/api/GradleException.java
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/Incubating.java b/subprojects/base-services/src/main/java/org/gradle/api/Incubating.java
new file mode 100644
index 0000000..2f3c781
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/Incubating.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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that a feature is incubating. This means that the feature is currently a work-in-progress and may
+ * change at any time.
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Incubating {
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java b/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java
index 814f643..b1b430f 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java
@@ -57,7 +57,10 @@ public enum JavaVersion {
 
         Matcher matcher = Pattern.compile("1\\.(\\d)(\\D.*)?").matcher(name);
         if (matcher.matches()) {
-            return values()[Integer.parseInt(matcher.group(1)) - 1];
+            int versionIdx = Integer.parseInt(matcher.group(1)) - 1;
+            if (versionIdx >= 0 && versionIdx < values().length) {
+                return values()[versionIdx];
+            }
         }
         throw new IllegalArgumentException(String.format("Could not determine java version from '%s'.", name));
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Named.java b/subprojects/base-services/src/main/java/org/gradle/api/Named.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/Named.java
rename to subprojects/base-services/src/main/java/org/gradle/api/Named.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Namer.java b/subprojects/base-services/src/main/java/org/gradle/api/Namer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/Namer.java
rename to subprojects/base-services/src/main/java/org/gradle/api/Namer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Transformer.java b/subprojects/base-services/src/main/java/org/gradle/api/Transformer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/Transformer.java
rename to subprojects/base-services/src/main/java/org/gradle/api/Transformer.java
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/UncheckedIOException.java b/subprojects/base-services/src/main/java/org/gradle/api/UncheckedIOException.java
new file mode 100644
index 0000000..1dc2f46
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/UncheckedIOException.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;
+
+/**
+ * <code>UncheckedIOException</code> is used to wrap an {@link java.io.IOException} into an unchecked exception.
+ */
+public class UncheckedIOException extends RuntimeException {
+    public UncheckedIOException() {
+    }
+
+    public UncheckedIOException(String message) {
+        super(message);
+    }
+
+    public UncheckedIOException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UncheckedIOException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
new file mode 100644
index 0000000..d47dd99
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Actions.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Transformer;
+import org.gradle.api.specs.Spec;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class Actions {
+
+    /**
+     * Creates an action implementation that simply does nothing.
+     *
+     * A new action instance is created each time.
+     *
+     * @return An action object with an empty implementation
+     */
+    public static <T> Action<T> doNothing() {
+        return new NullAction<T>();
+    }
+
+    private static class NullAction<T> implements Action<T>, Serializable {
+        public void execute(T t) {}
+    }
+
+    /**
+     * Creates an action that will call each of the given actions in order.
+     *
+     * @param actions The actions to make a composite of.
+     * @param <T> The type of the object that action is for
+     * @return The composite action.
+     */
+    public static <T> Action<T> composite(Action<? super T>... actions) {
+        final List<Action<? super T>> actionsCopy = new ArrayList<Action<? super T>>(actions.length);
+        Collections.addAll(actionsCopy, actions);
+        return new CompositeAction<T>(actionsCopy);
+   }
+
+    private static class CompositeAction<T> implements Action<T> {
+        private final Iterable<Action<? super T>> actions;
+
+        private CompositeAction(Iterable<Action<? super T>> actions) {
+            this.actions = actions;
+        }
+
+        public void execute(T item) {
+            for (Action<? super T> action : actions) {
+                action.execute(item);
+            }
+        }
+    }
+
+    /**
+     * Creates a new composite action, where the argument is first transformed.
+     *
+     * @param action The action.
+     * @param transformer The transformer to transform the argument with
+     * @param <T> The type the action is expecting (that the argument is transformed to)
+     * @param <I> The type of the original argument
+     * @return An action that transforms an object of type I to type O to give to the given action
+     */
+    public static <T, I> Action<I> transformBefore(final Action<? super T> action, final Transformer<? extends T, ? super I> transformer) {
+        return new TransformingActionAdapter<T, I>(transformer, action);
+    }
+
+    private static class TransformingActionAdapter<T, I> implements Action<I> {
+        private final Transformer<? extends T, ? super I> transformer;
+        private final Action<? super T> action;
+
+        private TransformingActionAdapter(Transformer<? extends T, ? super I> transformer, Action<? super T> action) {
+            this.transformer = transformer;
+            this.action = action;
+        }
+
+        public void execute(I thing) {
+            T transformed = transformer.transform(thing);
+            action.execute(transformed);
+        }
+    }
+
+    /**
+     * Adapts an action to a different type by casting the object before giving it to the action.
+     *
+     * @param actionType The type the action is expecting
+     * @param action The action
+     * @param <T> The type the action is expecting
+     * @param <I> The type before casting
+     * @return An action that casts the object to the given type before giving it to the given action
+     */
+    public static <T, I> Action<I> castBefore(final Class<T> actionType, final Action<? super T> action) {
+        return transformBefore(action, Transformers.cast(actionType));
+    }
+
+    /**
+     * Wraps the given runnable in an {@link Action}, where the execute implementation runs the runnable ignoring the argument.
+     *
+     * If the given runnable is {@code null}, the action returned is effectively a noop.
+     *
+     * @param runnable The runnable to run for the action execution.
+     * @return An action that runs the given runnable, ignoring the argument.
+     */
+    public static <T> Action<T> toAction(Runnable runnable) {
+        if (runnable == null) {
+            return Actions.doNothing();
+        } else {
+            return new RunnableActionAdapter<T>(runnable);
+        }
+    }
+
+    private static class RunnableActionAdapter<T> implements Action<T> {
+        private final Runnable runnable;
+
+        private RunnableActionAdapter(Runnable runnable) {
+            this.runnable = runnable;
+        }
+
+        public void execute(T t) {
+            runnable.run();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("RunnableActionAdapter{runnable=%s}", runnable);
+        }
+    }
+
+    /**
+     * Creates a new action that only forwards arguments on to the given filter is they are satisfied by the given spec.
+     *
+     * @param action The action to delegate filtered items to
+     * @param filter The spec to use to filter items by
+     * @param <T> The type of item the action expects
+     * @return A new action that only forwards arguments on to the given filter is they are satisfied by the given spec.
+     */
+    public static <T> Action<T> filter(Action<? super T> action, Spec<? super T> filter) {
+        return new FilteredAction<T>(action, filter);
+    }
+
+    private static class FilteredAction<T> implements Action<T> {
+        private final Spec<? super T> filter;
+        private final Action<? super T> action;
+
+        public FilteredAction(Action<? super T> action, Spec<? super T> filter) {
+            this.filter = filter;
+            this.action = action;
+        }
+
+        public void execute(T t) {
+            if (filter.isSatisfiedBy(t)) {
+                action.execute(t);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Cast.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Cast.java
new file mode 100644
index 0000000..505c6c7
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Cast.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;
+
+public abstract class Cast {
+
+    /**
+     * Casts the given object to the given type, providing a better error message than the default.
+     *
+     * The standard {@link Class#cast(Object)} method produces unsatisfactory error messages on some platforms
+     * when it fails. All this method does is provide a better, consistent, error message.
+     *
+     * This should be used whenever there is a chance the cast could fail. If in doubt, use this.
+     *
+     * @param outputType The type to cast the input to
+     * @param object The object to be cast
+     * @param <O> The type to be cast to
+     * @param <I> The type of the object to be vast
+     * @return The input object, cast to the output type
+     */
+
+    public static <O, I> O cast(Class<O> outputType, I object) {
+        try {
+            return outputType.cast(object);
+        } catch (ClassCastException e) {
+            throw new ClassCastException(String.format(
+                    "Failed to cast object %s of type %s to target type %s", object, object.getClass().getName(), outputType.getName()
+            ));
+        }
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/ErroringAction.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/ErroringAction.java
new file mode 100644
index 0000000..24d846d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/ErroringAction.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.internal;
+
+import org.gradle.api.Action;
+import org.gradle.internal.UncheckedException;
+
+/**
+ * Action adapter/implementation for action code that may throw exceptions.
+ *
+ * Implementations implement doExecute() (instead of execute()) which is allowed to throw checked exceptions.
+ * Any checked exceptions thrown will be wrapped as unchecked exceptions and re-thrown.
+ *
+ * How the exception is wrapped is subject to {@link UncheckedException#throwAsUncheckedException(Throwable)}.
+ *
+ * @param <T> The type of object which this action accepts.
+ */
+public abstract class ErroringAction<T> implements Action<T> {
+
+    public void execute(T thing) {
+        try {
+            doExecute(thing);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    protected abstract void doExecute(T thing) throws Exception;
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/HasInternalProtocol.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/HasInternalProtocol.java
new file mode 100644
index 0000000..9702306
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/HasInternalProtocol.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that there is an internal complementary protocol to the public type that is annotated with this.
+ *
+ * This should only be used on a type that is always assumed to also implement the internal protocol by Gradle internals.
+ *
+ * This exists to help anyone reading the source code realise that there is an internal component to the type.
+ */
+ at Retention(RetentionPolicy.SOURCE)
+ at Target({ElementType.TYPE})
+public @interface HasInternalProtocol {
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java
new file mode 100644
index 0000000..d464404
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.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.api.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+/**
+ * Various utilities for dealing with IO actions.
+ */
+public abstract class IoActions {
+
+    /**
+     * Gives a writer for the given file/encoding to the given write action, managing the streams.
+     *
+     * @param output The file to write to
+     * @param encoding The character encoding to write with
+     * @param action The action to write the actual content
+     */
+    public static void writeFile(File output, String encoding, Action<? super BufferedWriter> action) {
+        createFileWriteAction(output, encoding).execute(action);
+    }
+
+    /**
+     * Gives a writer (with the default file encoding) to the given write action, managing the streams.
+     *
+     * @param output The file to write to
+     * @param action The action to write the actual content
+     */
+    public static void writeFile(File output, Action<? super BufferedWriter> action) {
+        writeFile(output, Charset.defaultCharset().name(), action);
+    }
+
+    /**
+     * Creates an action that itself takes an action that will perform that actual writing to the file.
+     *
+     * All IO is deferred until the execution of the returned action.
+     *
+     * @param output The file to write to
+     * @param encoding The character encoding to write with
+     * @return An action that receives an action that performs the actual writing
+     */
+    public static Action<Action<? super BufferedWriter>> createFileWriteAction(File output, String encoding) {
+        return new FileWriterIoAction(output, encoding);
+    }
+
+    private static class FileWriterIoAction implements Action<Action<? super BufferedWriter>> {
+        private final File file;
+        private final String encoding;
+
+        private FileWriterIoAction(File file, String encoding) {
+            this.file = file;
+            this.encoding = encoding;
+        }
+
+        public void execute(Action<? super BufferedWriter> action) {
+            try {
+                File parentFile = file.getParentFile();
+                if (parentFile != null) {
+                    if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
+                        throw new IOException(String.format("Unable to create directory '%s'", parentFile));
+                    }
+                }
+                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), encoding));
+                try {
+                    action.execute(writer);
+                } finally {
+                    writer.close();
+                }
+            } catch (Exception e) {
+                throw new UncheckedIOException(String.format("Could not write to file '%s'.", file), e);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Transformers.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Transformers.java
new file mode 100644
index 0000000..c066c8e
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Transformers.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Namer;
+import org.gradle.api.Transformer;
+
+/**
+ * Utility transformers.
+ */
+public abstract class Transformers {
+
+    /**
+     * Creates a transformer that simply type casts the input to the given output type.
+     *
+     * @param outputType The type to cast the input to
+     * @param <O> The type of the transformed object
+     * @param <I> The type of the object to be transformed
+     * @return A transformer that simply type casts the input to the given output type.
+     */
+    public static <O, I> Transformer<O, I> cast(Class<O> outputType) {
+        return new CastingTransformer<O, I>(outputType);
+    }
+
+    private static class CastingTransformer<O, I> implements Transformer<O, I> {
+        final Class<O> outputType;
+
+        public CastingTransformer(Class<O> outputType) {
+            this.outputType = outputType;
+        }
+
+        public O transform(I input) {
+            return Cast.cast(outputType, input);
+        }
+    }
+
+    public static <T> Transformer<String, T> asString() {
+        return new ToStringTransformer<T>();
+    }
+
+    private static class ToStringTransformer<T> implements Transformer<String, T> {
+        public String transform(T original) {
+            return original == null ? null : original.toString();
+        }
+    }
+
+    /**
+     * Returns a transformer that names {@link Named} objects.
+     *
+     * Nulls are returned as null.
+     *
+     * @return The naming transformer.
+     */
+    public static Transformer<String, Named> name() {
+        return name(new Named.Namer());
+    }
+
+    /**
+     * Returns a transformer that names objects with the given {@link Namer}
+     * @param namer The namer to name the objects with
+     * @param <T> The type of objects to be named
+     * @return The naming transformer.
+     */
+    public static <T> Transformer<String, T> name(Namer<? super T> namer) {
+        return new ToNameTransformer<T>(namer);
+    }
+
+    private static class ToNameTransformer<T> implements Transformer<String, T> {
+        private final Namer<? super T> namer;
+
+        public ToNameTransformer(Namer<? super T> namer) {
+            this.namer = namer;
+        }
+
+        public String transform(T thing) {
+            return thing == null ? null : namer.determineName(thing);
+        }
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/specs/CompositeSpec.java b/subprojects/base-services/src/main/java/org/gradle/api/specs/CompositeSpec.java
new file mode 100644
index 0000000..f3ce645
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/specs/CompositeSpec.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link org.gradle.api.specs.Spec} which aggregates a sequence of other {@code Spec} instances.
+ *
+ * @author Hans Dockter
+ * @param <T> The target type for this Spec
+ */
+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);
+    }
+
+    protected CompositeSpec(Iterable<? extends Spec<? super T>> specs) {
+        this.specs = new ArrayList<Spec<? super T>>();
+        for (Spec<? super T> spec : specs) {
+            this.specs.add(spec);
+        }
+    }
+
+    public List<Spec<? super T>> getSpecs() {
+        return Collections.unmodifiableList(specs);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof CompositeSpec)) {
+            return false;
+        }
+
+        CompositeSpec that = (CompositeSpec) o;
+
+        if (specs != null ? !specs.equals(that.specs) : that.specs != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return specs != null ? specs.hashCode() : 0;
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/specs/NotSpec.java b/subprojects/base-services/src/main/java/org/gradle/api/specs/NotSpec.java
new file mode 100644
index 0000000..6938d8b
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/specs/NotSpec.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.specs;
+
+/**
+ * A {@link org.gradle.api.specs.Spec} implementation which negates another {@code Spec}.
+ * 
+ * @author Hans Dockter
+ * @param <T> The target type for this Spec
+ */
+public class NotSpec<T> implements Spec<T> {
+    private Spec<? super T> sourceSpec;
+
+    public NotSpec(Spec<? super T> sourceSpec) {
+        this.sourceSpec = sourceSpec;
+    }
+
+    public boolean isSatisfiedBy(T element) {
+        return !sourceSpec.isSatisfiedBy(element);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/specs/OrSpec.java b/subprojects/base-services/src/main/java/org/gradle/api/specs/OrSpec.java
new file mode 100644
index 0000000..9f63a61
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/specs/OrSpec.java
@@ -0,0 +1,49 @@
+/*
+ * 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.specs;
+
+import java.util.List;
+
+/**
+ * A {@link CompositeSpec} which requires any one of its specs to be true in order to evaluate to
+ * true. Uses lazy evaluation.
+ *
+ * @author Hans Dockter
+ * @param <T> The target type for this Spec
+ */
+public class OrSpec<T> extends CompositeSpec<T> {
+    public OrSpec(Spec<? super T>... specs) {
+        super(specs);
+    }
+
+    public OrSpec(Iterable<? extends Spec<? super T>> specs) {
+        super(specs);
+    }
+
+    public boolean isSatisfiedBy(T object) {
+        List<Spec<? super T>> specs = getSpecs();
+        if (specs.isEmpty()) {
+            return true;
+        }
+
+        for (Spec<? super T> spec : specs) {
+            if (spec.isSatisfiedBy(object)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/Spec.java b/subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/specs/Spec.java
rename to subprojects/base-services/src/main/java/org/gradle/api/specs/Spec.java
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
index 548a4ee..327850a 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
@@ -30,12 +30,15 @@ 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() {
     }
 
-    public CompositeStoppable(Iterable<?> elements) {
-        add(elements);
+    public static CompositeStoppable stoppable(Object... elements) {
+        return new CompositeStoppable().add(elements);
+    }
+
+    public static CompositeStoppable stoppable(Iterable<?> elements) {
+        return new CompositeStoppable().add(elements);
     }
 
     public CompositeStoppable add(Iterable<?> elements) {
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Factories.java b/subprojects/base-services/src/main/java/org/gradle/internal/Factories.java
new file mode 100644
index 0000000..0754a14
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Factories.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.internal;
+
+public abstract class Factories {
+    public static <T> Factory<T> toFactory(final Runnable runnable) {
+        return new Factory<T>() {
+            public T create() {
+                runnable.run();
+                return null;
+            }
+        };
+    }
+}
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
index 49d9559..7a702a6 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
@@ -15,12 +15,42 @@
  */
 package org.gradle.internal;
 
-import java.util.Map;
+import java.util.*;
 
 /**
  * Provides access to frequently used system properties.
  */
 public class SystemProperties {
+    private static final Set<String> STANDARD_PROPERTIES = new HashSet<String>(Arrays.asList(
+            "java.version",
+            "java.vendor",
+            "java.vendor.url",
+            "java.home",
+            "java.vm.specification.version",
+            "java.vm.specification.vendor",
+            "java.vm.specification.name",
+            "java.vm.version",
+            "java.vm.vendor",
+            "java.vm.name",
+            "java.specification.version",
+            "java.specification.vendor",
+            "java.specification.name",
+            "java.class.version",
+            "java.class.path",
+            "java.library.path",
+            "java.io.tmpdir",
+            "java.compiler",
+            "java.ext.dirs",
+            "os.name",
+            "os.arch",
+            "os.version",
+            "file.separator",
+            "path.separator",
+            "line.separator",
+            "user.name",
+            "user.home",
+            "user.dir"));
+
     @SuppressWarnings("unchecked")
     public static Map<String, String> asMap() {
         return (Map) System.getProperties();
@@ -41,4 +71,12 @@ public class SystemProperties {
     public static String getJavaVersion() {
         return System.getProperty("java.version");
     }
+
+    /**
+     * Returns the keys that are guaranteed to be contained in System.getProperties() by default,
+     * as specified in the Javadoc for that method.
+     */
+    public static Set<String> getStandardProperties() {
+        return STANDARD_PROPERTIES;
+    }
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java
index d644701..6198267 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java
@@ -45,6 +45,11 @@ public class DefaultClassPath implements ClassPath, Serializable {
         this(Arrays.asList(files));
     }
 
+    @Override
+    public String toString() {
+        return files.toString();
+    }
+
     public boolean isEmpty() {
         return files.isEmpty();
     }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java
index 508e28d..95377e4 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java
@@ -33,7 +33,7 @@ public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
 
     public void stop() {
         try {
-            new CompositeStoppable(executors).stop();
+            CompositeStoppable.stoppable(executors).stop();
         } finally {
             executors.clear();
         }
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
index b2800a4..5951a2a 100644
--- 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
@@ -266,14 +266,6 @@ public class Jvm implements JavaInfo {
          * {@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");
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java
index 6864722..65a2ac0 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java
@@ -15,8 +15,6 @@
  */
 package org.gradle.internal.reflect;
 
-import org.gradle.internal.UncheckedException;
-
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
@@ -40,9 +38,9 @@ public class DirectInstantiator implements Instantiator {
             }
             return type.cast(matches.get(0).newInstance(params));
         } catch (InvocationTargetException e) {
-            throw UncheckedException.throwAsUncheckedException(e.getCause());
+            throw new ObjectInstantiationException(type, e.getCause());
         } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
+            throw new ObjectInstantiationException(type, e);
         }
     }
 
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java
index e00aa04..6c50e84 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java
@@ -22,7 +22,9 @@ public interface Instantiator {
 
     /**
      * Create a new instance of T, using {@code parameters} as the construction parameters.
+     *
+     * @throws ObjectInstantiationException On failure to create the new instance.
      */
-    <T> T newInstance(Class<T> type, Object... parameters);
+    <T> T newInstance(Class<T> type, Object... parameters) throws ObjectInstantiationException;
 
 }
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/ObjectInstantiationException.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/ObjectInstantiationException.java
new file mode 100644
index 0000000..801321e
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/ObjectInstantiationException.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.reflect;
+
+public class ObjectInstantiationException extends RuntimeException {
+    public ObjectInstantiationException(Class<?> targetType, Throwable throwable) {
+        super(String.format("Could not create an instance of type %s.", targetType.getName()), throwable);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/AbstractServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/AbstractServiceRegistry.java
new file mode 100644
index 0000000..2a90634
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/AbstractServiceRegistry.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.internal.service;
+
+import org.gradle.internal.Factory;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+public abstract class AbstractServiceRegistry implements ServiceRegistry {
+    public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException {
+        if (serviceType instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) serviceType;
+            if (parameterizedType.getRawType().equals(Factory.class)) {
+                Type typeArg = parameterizedType.getActualTypeArguments()[0];
+                if (typeArg instanceof Class) {
+                    return getFactory((Class) typeArg);
+                }
+                if (typeArg instanceof WildcardType) {
+                    WildcardType wildcardType = (WildcardType) typeArg;
+                    if (wildcardType.getLowerBounds().length == 1 && wildcardType.getUpperBounds().length == 1) {
+                        if (wildcardType.getLowerBounds()[0] instanceof Class && wildcardType.getUpperBounds()[0].equals(Object.class)) {
+                            return getFactory((Class<Object>) wildcardType.getLowerBounds()[0]);
+                        }
+                    }
+                    if (wildcardType.getLowerBounds().length == 0 && wildcardType.getUpperBounds().length == 1) {
+                        if (wildcardType.getUpperBounds()[0] instanceof Class) {
+                            return getFactory((Class<Object>) wildcardType.getUpperBounds()[0]);
+                        }
+                    }
+                }
+            }
+        }
+        if (serviceType instanceof Class) {
+            return get((Class) serviceType);
+        }
+        throw new UnsupportedOperationException(String.format("Cannot locate service of type %s yet.", serviceType));
+    }
+
+    public <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException {
+        if (serviceType.equals(Factory.class)) {
+            throw new IllegalArgumentException("Cannot locate service of raw type Factory. Use getFactory() or get(Type) instead.");
+        }
+        if (serviceType.isArray()) {
+            throw new IllegalArgumentException(String.format("Cannot locate service of array type %s[].", serviceType.getComponentType().getSimpleName()));
+        }
+        if (serviceType.isAnnotation()) {
+            throw new IllegalArgumentException(String.format("Cannot locate service of annotation type @%s.", serviceType.getSimpleName()));
+        }
+        return doGet(serviceType);
+    }
+
+    protected abstract <T> T doGet(Class<T> serviceType);
+
+    public <T> T newInstance(Class<T> type) {
+        return getFactory(type).create();
+    }
+
+}
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
index a5cdde3..fc455ee 100644
--- 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
@@ -44,7 +44,7 @@ import java.util.*;
  *
  * <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 {
+public class DefaultServiceRegistry extends AbstractServiceRegistry {
     private final List<Provider> providers = new LinkedList<Provider>();
     private final OwnServices ownServices;
     private final List<Provider> registeredProviders;
@@ -128,14 +128,14 @@ public class DefaultServiceRegistry implements ServiceRegistry {
      */
     public void close() {
         try {
-            new CompositeStoppable(providers).stop();
+            CompositeStoppable.stoppable(providers).stop();
         } finally {
             closed = true;
             providers.clear();
         }
     }
 
-    public <T> T get(Class<T> serviceType) throws IllegalArgumentException {
+    public <T> T doGet(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));
@@ -169,10 +169,6 @@ public class DefaultServiceRegistry implements ServiceRegistry {
                 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);
@@ -228,7 +224,7 @@ public class DefaultServiceRegistry implements ServiceRegistry {
         }
 
         public void stop() {
-            new CompositeStoppable(providers).stop();
+            CompositeStoppable.stoppable(providers).stop();
         }
 
         public void add(Provider provider) {
@@ -251,7 +247,7 @@ public class DefaultServiceRegistry implements ServiceRegistry {
 
         public void stop() {
             try {
-                new CompositeStoppable().add(instance).stop();
+                CompositeStoppable.stoppable(instance).stop();
             } finally {
                 instance = null;
             }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java
index 30232c7..dacebfa 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java
@@ -18,6 +18,7 @@ package org.gradle.internal.service;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.reflect.ObjectInstantiationException;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
 /**
  * Uses the Jar service resource specification to locate service implementations.
  */
-public class ServiceLocator implements ServiceRegistry {
+public class ServiceLocator extends AbstractServiceRegistry {
     private final ClassLoader classLoader;
     private final Map<Class<?>, Object> implementations = new ConcurrentHashMap<Class<?>, Object>();
 
@@ -38,7 +39,7 @@ public class ServiceLocator implements ServiceRegistry {
         this.classLoader = classLoader;
     }
 
-    public <T> T get(Class<T> serviceType) throws UnknownServiceException {
+    public <T> T doGet(Class<T> serviceType) throws UnknownServiceException {
         synchronized (implementations) {
             T implementation = serviceType.cast(implementations.get(serviceType));
             if (implementation == null) {
@@ -68,10 +69,6 @@ public class ServiceLocator implements ServiceRegistry {
         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 {
@@ -137,7 +134,7 @@ public class ServiceLocator implements ServiceRegistry {
             Instantiator instantiator = new DirectInstantiator();
             try {
                 return instantiator.newInstance(implementationClass, params);
-            } catch (Throwable t) {
+            } catch (ObjectInstantiationException t) {
                 throw new RuntimeException(String.format("Could not create an implementation of service '%s'.", serviceType.getName()), t);
             }
         }
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
index 2db554d..350f1af 100644
--- 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
@@ -17,12 +17,14 @@ package org.gradle.internal.service;
 
 import org.gradle.internal.Factory;
 
+import java.lang.reflect.Type;
+
 /**
  * A registry of services.
  */
 public interface ServiceRegistry {
     /**
-     * Locates the service of the given type. There is a single instance for each service type.
+     * Locates the service of the given type.
      *
      * @param serviceType The service type.
      * @param <T>         The service type.
@@ -33,6 +35,16 @@ public interface ServiceRegistry {
     <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException;
 
     /**
+     * Locates the service of the given type.
+     *
+     * @param serviceType The service type.
+     * @return The service instance. Never returns null.
+     * @throws UnknownServiceException When there is no service of the given type available.
+     * @throws ServiceLookupException On failure to lookup the specified service.
+     */
+    Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException;
+
+    /**
      * Locates a factory which can create services of the given type.
      *
      * @param type The service type that the factory should create.
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
index d59c88b..868ba39 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
@@ -19,6 +19,8 @@ package org.gradle.internal.service;
 import org.gradle.internal.Factory;
 import org.gradle.internal.concurrent.Synchronizer;
 
+import java.lang.reflect.Type;
+
 /**
  * by Szczepan Faber, created at: 11/24/11
  */
@@ -38,6 +40,14 @@ public class SynchronizedServiceRegistry implements ServiceRegistry {
         });
     }
 
+    public Object get(final Type serviceType) throws UnknownServiceException, ServiceLookupException {
+        return synchronizer.synchronize(new Factory<Object>() {
+            public Object 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() {
diff --git a/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java b/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
new file mode 100644
index 0000000..bcbb4a5
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/util/CollectionUtils.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.api.Transformer;
+import org.gradle.api.internal.Transformers;
+import org.gradle.api.specs.Spec;
+
+import java.lang.reflect.Array;
+import java.util.*;
+
+import static org.gradle.api.internal.Cast.cast;
+
+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 <K, V> Map<K, V> filter(Map<K, V> map, Spec<Map.Entry<K, V>> filter) {
+        return filter(map, new HashMap<K, V>(), filter);
+    }
+
+    public static <K, V> Map<K, V> filter(Map<K, V> map, Map<K, V> destination, Spec<Map.Entry<K, V>> filter) {
+        for (Map.Entry<K, V> entry : map.entrySet()) {
+            if (filter.isSatisfiedBy(entry)) {
+                destination.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return destination;
+    }
+
+    public static <R, I> R[] collectArray(I[] list, Class<R> newType, Transformer<R, I> transformer) {
+        return collectArray(list, (R[]) Array.newInstance(newType, list.length), transformer);
+    }
+
+    public static <R, I> R[] collectArray(I[] list, R[] destination, Transformer<R, I> transformer) {
+        assert list.length <= destination.length;
+        for (int i = 0; i < list.length; ++i) {
+            destination[i] = transformer.transform(list[i]);
+        }
+        return destination;
+    }
+
+    public static <R, I> List<R> collect(List<? extends I> list, Transformer<R, ? super I> transformer) {
+        return collect(list, new ArrayList<R>(list.size()), transformer);
+    }
+
+    public static <R, I> List<R> collect(I[] list, Transformer<R, ? super I> transformer) {
+        return collect(Arrays.asList(list), transformer);
+    }
+
+    public static <R, I> Set<R> collect(Set<? extends I> set, Transformer<R, ? super 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, ? super I> transformer) {
+        for (I item : source) {
+            destination.add(transformer.transform(item));
+        }
+        return destination;
+    }
+
+    public static List<String> toStringList(Iterable<?> iterable) {
+        return collect(iterable, new LinkedList<String>(), Transformers.asString());
+    }
+
+    /**
+     * Recursively unpacks all the given things into a flat list.
+     *
+     * Nulls are not removed, they are left intact.
+     *
+     * @param things The things to flatten
+     * @return A flattened list of the given things
+     */
+    public static List<?> flattenToList(Object... things) {
+        return flattenToList(Object.class, things);
+    }
+
+    /**
+     * Recursively unpacks all the given things into a flat list, ensuring they are of a certain type.
+     *
+     * Nulls are not removed, they are left intact.
+     *
+     * If a non null object cannot be cast to the target type, a ClassCastException will be thrown.
+     *
+     * @param things The things to flatten
+     * @param <T> The target type in the flattened list
+     * @return A flattened list of the given things
+     */
+    public static <T> List<? extends T> flattenToList(Class<T> type, Object... things) {
+        if (things == null) {
+            return Collections.singletonList(null);
+        } else if (things.length == 0) {
+            return Collections.emptyList();
+        } else if (things.length == 1) {
+            Object thing = things[0];
+
+            if (thing == null) {
+                return Collections.singletonList(null);
+            }
+
+            if (thing.getClass().isArray()) {
+                Object[] thingArray = (Object[]) thing;
+                List<T> list = new ArrayList<T>(thingArray.length);
+                for (Object thingThing : thingArray) {
+                    list.addAll(flattenToList(type, thingThing));
+                }
+                return list;
+            }
+
+            if (thing instanceof Iterable) {
+                Iterable<?> iterableThing = (Iterable<?>) thing;
+                List<T> list = new ArrayList<T>();
+                for (Object thingThing : iterableThing) {
+                    list.addAll(flattenToList(type, thingThing));
+                }
+                return list;
+            }
+
+            return Collections.singletonList(cast(type, thing));
+        } else {
+            List<T> list = new ArrayList<T>();
+            for (Object thing : things) {
+                list.addAll(flattenToList(type, thing));
+            }
+            return list;
+        }
+    }
+
+    public static <T> List<T> toList(Iterable<? extends T> things) {
+        if (things == null) {
+            return new ArrayList<T>(0);
+        }
+        if (things instanceof List) {
+            return (List<T>) things;
+        }
+
+        List<T> list = new ArrayList<T>();
+        for (T thing : things) {
+            list.add(thing);
+        }
+        return list;
+    }
+
+    public static <E> List<E> compact(List<E> list) {
+        boolean foundAtLeastOneNull = false;
+        List<E> compacted = null;
+        int i = 0;
+
+        for (E element : list) {
+            if (element == null) {
+                if (!foundAtLeastOneNull) {
+                    compacted = new ArrayList<E>(list.size());
+                    if (i > 0) {
+                        compacted.addAll(list.subList(0, i));
+                    }
+                }
+                foundAtLeastOneNull = true;
+            } else if (foundAtLeastOneNull) {
+                compacted.add(element);
+            }
+            ++i;
+        }
+
+        return foundAtLeastOneNull ? compacted : list;
+    }
+
+    public static <C extends Collection<String>> C stringize(Iterable<?> source, C destination) {
+        return collect(source, destination, Transformers.asString());
+    }
+
+    public static List<String> stringize(List<?> source) {
+        return stringize(source, new ArrayList<String>(source.size()));
+    }
+
+    public static <E> boolean replace(List<E> list, Spec<? super E> filter, Transformer<E, ? super E> transformer) {
+        boolean replaced = false;
+        int i = 0;
+        for (E it : list) {
+            if (filter.isSatisfiedBy(it)) {
+                list.set(i, transformer.transform(it));
+                replaced = true;
+            }
+            ++i;
+        }
+        return replaced;
+    }
+
+    public static <K, V> void collectMap(Map<K, V> destination, Iterable<? extends V> items, Transformer<? extends K, ? super V> keyGenerator) {
+        for (V item : items) {
+            destination.put(keyGenerator.transform(item), item);
+        }
+    }
+
+    public static <K, V> Map<K, V> collectMap(Iterable<? extends V> items, Transformer<? extends K, ? super V> keyGenerator) {
+        Map<K, V> map = new LinkedHashMap<K, V>();
+        collectMap(map, items, keyGenerator);
+        return map;
+    }
+
+    public static <T> boolean every(Iterable<T> things, Spec<? super T> predicate) {
+        for (T thing : things) {
+            if (!predicate.isSatisfiedBy(thing)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Utility for adding an iterable to a collection.
+     *
+     * @param t1 The collection to add to
+     * @param t2 The iterable to add each item of to the collection
+     * @param <T> The element type of t1
+     * @return t1
+     */
+    public static <T> Collection<T> addAll(Collection<T> t1, Iterable<? extends T> t2) {
+        for (T t : t2) {
+            t1.add(t);
+        }
+        return t1;
+    }
+
+    /**
+     * The result of diffing two sets.
+     *
+     * @param <T> The type of element the sets contain
+     * @see CollectionUtils#diffSetsBy(java.util.Set, java.util.Set, org.gradle.api.Transformer)
+     */
+    public static class SetDiff<T> {
+        public static class Pair<T> {
+            public T left;
+            public T right;
+        }
+
+        public Set<T> leftOnly = new HashSet<T>();
+        public Set<Pair<T>> common = new HashSet<Pair<T>>();
+        public Set<T> rightOnly = new HashSet<T>();
+    }
+
+    /**
+     * Provides a “diff report” of how the two sets are similar and how they are different, comparing the entries by some aspect.
+     *
+     * The transformer is used to generate the value to use to compare the entries by. That is, the entries are not compared by equals by an attribute or characteristic.
+     *
+     * The transformer is expected to produce a unique value for each entry in a single set. Behaviour is undefined if this condition is not met.
+     *
+     * @param left The set on the “left” side of the comparison.
+     * @param right The set on the “right” side of the comparison.
+     * @param compareBy Provides the value to compare entries from either side by
+     * @param <T> The type of the entry objects
+     * @return A representation of the difference
+     */
+    public static <T> SetDiff<T> diffSetsBy(Set<? extends T> left, Set<? extends T> right, Transformer<?, T> compareBy) {
+        if (left == null) {
+            throw new NullPointerException("'left' set is null");
+        }
+        if (right == null) {
+            throw new NullPointerException("'right' set is null");
+        }
+
+        SetDiff<T> setDiff = new SetDiff<T>();
+
+        Map<Object, T> indexedLeft = collectMap(left, compareBy);
+        Map<Object, T> indexedRight = collectMap(right, compareBy);
+
+        for (Map.Entry<Object, T> leftEntry : indexedLeft.entrySet()) {
+            T rightValue = indexedRight.remove(leftEntry.getKey());
+            if (rightValue == null) {
+                setDiff.leftOnly.add(leftEntry.getValue());
+            } else {
+                SetDiff.Pair<T> pair = new SetDiff.Pair<T>();
+                pair.left = leftEntry.getValue();
+                pair.right = rightValue;
+                setDiff.common.add(pair);
+            }
+        }
+
+        for (T rightValue : indexedRight.values()) {
+            setDiff.rightOnly.add(rightValue);
+        }
+
+        return setDiff;
+    }
+
+    /**
+     * Creates a string with {@code toString()} of each object with the given separator.
+     *
+     * <pre>
+     * expect:
+     * join(",", new Object[]{"a"}) == "a"
+     * join(",", new Object[]{"a", "b", "c"}) == "a,b,c"
+     * join(",", new Object[]{}) == ""
+     * </pre>
+     *
+     * The {@code separator} must not be null and {@code objects} must not be null.
+     *
+     * @param separator The string by which to join each string representation
+     * @param objects The objects to join the string representations of
+     * @return The joined string
+     */
+    public static String join(String separator, Object[] objects) {
+        return join(separator, objects == null ? null : Arrays.asList(objects));
+    }
+
+    /**
+     * Creates a string with {@code toString()} of each object with the given separator.
+     *
+     * <pre>
+     * expect:
+     * join(",", ["a"]) == "a"
+     * join(",", ["a", "b", "c"]) == "a,b,c"
+     * join(",", []) == ""
+     * </pre>
+     *
+     * The {@code separator} must not be null and {@code objects} must not be null.
+     *
+     * @param separator The string by which to join each string representation
+     * @param objects The objects to join the string representations of
+     * @return The joined string
+     */
+    public static String join(String separator, Iterable<?> objects) {
+        if (separator == null) {
+            throw new NullPointerException("The 'separator' cannot be null");
+        }
+        if (objects == null) {
+            throw new NullPointerException("The 'objects' cannot be null");
+        }
+
+        boolean first = true;
+        StringBuilder string = new StringBuilder();
+        for (Object object : objects) {
+            if (!first) {
+                string.append(separator);
+            }
+            string.append(object.toString());
+            first = false;
+        }
+        return string.toString();
+    }
+
+    public static class InjectionStep<T, I> {
+        private final T target;
+        private final I item;
+
+        public InjectionStep(T target, I item) {
+            this.target = target;
+            this.item = item;
+        }
+
+        public T getTarget() {
+            return target;
+        }
+
+        public I getItem() {
+            return item;
+        }
+    }
+
+    public static <T, I> T inject(T target, Iterable<? extends I> items, Action<InjectionStep<T, I>> action) {
+        if (target == null) {
+            throw new NullPointerException("The 'target' cannot be null");
+        }
+        if (items == null) {
+            throw new NullPointerException("The 'items' cannot be null");
+        }
+        if (action == null) {
+            throw new NullPointerException("The 'action' cannot be null");
+        }
+
+        for (I item : items) {
+            action.execute(new InjectionStep<T, I>(target, item));
+        }
+        return target;
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy
index be57cf3..e8438ac 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy
@@ -31,6 +31,7 @@ public class JavaVersionSpec extends Specification {
         JavaVersion.VERSION_1_5.toString() == "1.5"
         JavaVersion.VERSION_1_6.toString() == "1.6"
         JavaVersion.VERSION_1_7.toString() == "1.7"
+        JavaVersion.VERSION_1_8.toString() == "1.8"
     }
 
     def convertsStringToVersion() {
@@ -59,6 +60,7 @@ public class JavaVersionSpec extends Specification {
         conversionFails("  ");
 
         conversionFails("1.54");
+        conversionFails("1.9");
         conversionFails("1.10");
         conversionFails("2.0");
         conversionFails("1_4");
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
new file mode 100644
index 0000000..cf0185d
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Transformer
+import org.gradle.api.specs.Spec
+import spock.lang.Specification
+
+import static org.gradle.api.internal.Actions.*
+
+class ActionsTest extends Specification {
+
+    def "do nothing indeed does nothing"() {
+        given:
+        def thing = Mock(Object)
+
+        when:
+        doNothing().execute(thing)
+
+        then:
+        0 * thing._(*_)
+    }
+
+    def "can do nothing on null"() {
+        when:
+        doNothing().execute(null)
+
+        then:
+        notThrown(Throwable)
+    }
+
+    def "transform before"() {
+        given:
+        def action = Mock(Action)
+
+        when:
+        transformBefore(action, new Transformer<Integer, String>() {
+            Integer transform(String original) {
+                Integer.valueOf(original, 10) * 2
+            }
+
+        }).execute("1")
+
+        then:
+        1 * action.execute(2)
+    }
+
+    def "cast before"() {
+        given:
+        def action = Mock(Action)
+
+        when:
+        castBefore(Integer, action).execute("1")
+
+        then:
+        def e = thrown(ClassCastException)
+
+        when:
+        castBefore(CharSequence, action).execute("1")
+
+        then:
+        1 * action.execute("1")
+    }
+
+    def "adapting runnables"() {
+        given:
+        def runnable = Mock(Runnable)
+        def arg = Mock(Object)
+
+        when:
+        toAction(runnable).execute(arg)
+
+        then:
+        0 * arg._(*_)
+        1 * runnable.run()
+    }
+
+    def "adapting null runnables"() {
+        when:
+        toAction((Runnable) null).execute("foo")
+
+        then:
+        notThrown(Exception)
+    }
+
+    def "filtered action fires for matching"() {
+        given:
+        def called = false
+        def action = filter(action { called = true }, spec { true })
+
+        when:
+        action.execute "object"
+
+        then:
+        called
+    }
+
+    def "filtered action doesnt fire for not matching"() {
+        given:
+        def called = false
+        def action = filter(action { called = true }, spec { false })
+
+        when:
+        action.execute "object"
+
+        then:
+        !called
+    }
+
+    protected Spec spec(Closure spec) {
+        spec as Spec
+    }
+
+    protected action(Closure action) {
+        action as Action
+    }
+
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/CastTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/CastTest.groovy
new file mode 100644
index 0000000..049e1d3
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/CastTest.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.api.internal
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.Cast.cast
+
+class CastTest extends Specification {
+
+    def "casting"() {
+        given:
+        def arg = "1"
+
+        when:
+        cast(Integer, arg)
+
+        then:
+        def e = thrown(ClassCastException)
+        e.message == "Failed to cast object $arg of type ${arg.class.name} to target type ${Integer.name}"
+
+        when:
+        def result = cast(CharSequence, arg)
+
+        then:
+        notThrown(ClassCastException)
+        result.is arg
+    }
+
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ErroringActionTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ErroringActionTest.groovy
new file mode 100644
index 0000000..01e6a36
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ErroringActionTest.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
+
+import spock.lang.Specification
+
+class ErroringActionTest extends Specification {
+
+    def "checked exceptions are wrapped"() {
+        when:
+        new ErroringAction() {
+            @Override
+            void doExecute(Object thing) {
+                throw new Exception()
+            }
+        }.execute("foo")
+
+        then:
+        thrown(RuntimeException)
+    }
+
+    def "unchecked exceptions are passed through"() {
+        given:
+        def e = new RuntimeException()
+
+        when:
+        new ErroringAction() {
+            @Override
+            void doExecute(Object thing) {
+                throw e
+            }
+        }.execute("foo")
+
+        then:
+        def t = thrown(RuntimeException)
+        t.is(e)
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
new file mode 100644
index 0000000..3f82187
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.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.api.internal
+
+import org.gradle.api.Action
+import org.gradle.api.UncheckedIOException
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.api.internal.IoActions.createFileWriteAction
+import static org.gradle.api.internal.IoActions.writeFile
+
+class IoActionsTest extends Specification {
+
+    @Rule TemporaryFolder tmp
+
+    def "can use file action to write to file"() {
+        given:
+        def file = tmp.file("foo.txt")
+
+        when:
+        createFileWriteAction(file, "UTF-8").execute(new Action<Writer>() {
+            void execute(Writer writer) {
+                writer.write("bar")
+            }
+        })
+
+        then:
+        file.text == "bar"
+    }
+
+    def "fails to write to file when can't create parent dir"() {
+        given:
+        tmp.createFile("base")
+        def file = tmp.file("base/foo.txt")
+        def action = Mock(Action)
+
+        when:
+        createFileWriteAction(file, "UTF-8").execute(action)
+
+        then:
+        0 * action.execute(_)
+        def e = thrown UncheckedIOException
+        e.cause instanceof IOException
+        e.cause.message.startsWith("Unable to create directory")
+    }
+
+    def "can write file"() {
+        given:
+        def file = tmp.file("foo.txt")
+        def enc = "utf-8"
+
+        when:
+        writeFile(file, enc, new Action() {
+            void execute(writer) {
+                writer.append("bar⌘")
+            }
+        })
+
+        then:
+        file.getText(enc) == "bar⌘"
+    }
+
+    def "can write file with default encoding"() {
+        given:
+        def file = tmp.file("foo.txt")
+
+        when:
+        writeFile(file, new Action() {
+            void execute(writer) {
+                writer.append("bar⌘")
+            }
+        })
+
+        then:
+        file.text == "bar⌘"
+    }
+
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/TransformersTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/TransformersTest.groovy
new file mode 100644
index 0000000..6a95706
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/TransformersTest.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
+
+import org.gradle.api.Named
+import org.gradle.api.Namer
+import spock.lang.Specification
+
+import static org.gradle.api.internal.Transformers.*
+
+class TransformersTest extends Specification {
+
+    def "casting"() {
+        given:
+        def arg = "1"
+
+        when:
+        cast(Integer).transform(arg)
+
+        then:
+        def e = thrown(ClassCastException)
+        e.message == "Failed to cast object $arg of type ${arg.class.name} to target type ${Integer.name}"
+
+        when:
+        def result = cast(CharSequence).transform(arg)
+
+        then:
+        notThrown(ClassCastException)
+        result.is arg
+    }
+
+    def "as string"() {
+        expect:
+        asString().transform(1) == "1"
+        asString().transform(null) == null
+    }
+
+    def "naming"() {
+        expect:
+        name().transform(named("foo")) == "foo"
+        name().transform(named(null)) == null
+
+        and:
+        def namer = new Namer() {
+            String determineName(Object object) {
+                object.toString()
+            }
+        }
+
+        name(namer).transform(3) == "3"
+    }
+
+    Named named(String name) {
+        new Named() {
+            String getName() {
+                name
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/FactoriesTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/FactoriesTest.groovy
new file mode 100644
index 0000000..38aa5c7
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/FactoriesTest.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.internal
+
+import spock.lang.Specification
+
+class FactoriesTest extends Specification {
+    def "factory runs given runnable and returns null"() {
+        Runnable r = Mock()
+        Factory<String> factory = Factories.toFactory(r)
+
+        when:
+        def result = factory.create()
+
+        then:
+        result == null
+
+        and:
+        1 * r.run()
+        0 * r._
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy
new file mode 100644
index 0000000..c1a1102
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.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.internal
+
+import spock.lang.Specification
+
+class SystemPropertiesTest extends Specification {
+    def "can be queried for standard system properties"() {
+        expect:
+        SystemProperties.standardProperties.contains("os.name")
+        !SystemProperties.standardProperties.contains("foo.bar")
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy
index 60475be..4798d0b 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy
@@ -17,8 +17,6 @@ package org.gradle.internal.reflect
 
 import spock.lang.Specification
 
-import org.gradle.internal.UncheckedException
-
 class DirectInstantiatorTest extends Specification {
     final DirectInstantiator instantiator = new DirectInstantiator()
 
@@ -69,15 +67,16 @@ class DirectInstantiatorTest extends Specification {
         instantiator.newInstance(TypeWithAmbiguousConstructor, "param")
 
         then:
-        IllegalArgumentException e = thrown()
-        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [param]."
+        ObjectInstantiationException e = thrown()
+        e.cause instanceof IllegalArgumentException
+        e.cause.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]."
+        e.cause.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [true]."
     }
 
     def "fails when target class has no matching public constructor"() {
@@ -87,22 +86,23 @@ class DirectInstantiatorTest extends Specification {
         instantiator.newInstance(SomeType, param)
 
         then:
-        IllegalArgumentException e = thrown()
-        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [${param}]."
+        ObjectInstantiationException e = thrown()
+        e.cause instanceof IllegalArgumentException
+        e.cause.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]."
+        e.cause.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]."
+        e.cause.message == "Could not find any public constructor for ${SomeType} which accepts parameters [false]."
 
         when:
 
@@ -110,7 +110,7 @@ class DirectInstantiatorTest extends Specification {
 
         then:
         e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [a]."
+        e.cause.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [a]."
 
         when:
 
@@ -118,7 +118,7 @@ class DirectInstantiatorTest extends Specification {
 
         then:
         e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [22]."
+        e.cause.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [22]."
 
         when:
 
@@ -126,24 +126,16 @@ class DirectInstantiatorTest extends Specification {
 
         then:
         e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [null]."
+        e.cause.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [null]."
     }
 
-    def "rethrows unchecked exception thrown by constructor"() {
+    def "wraps 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()
+        ObjectInstantiationException e = thrown()
+        e.cause instanceof RuntimeException
         e.cause.message == 'broken'
     }
 }
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
index c1d03b9..d38d3e5 100755
--- 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
@@ -23,12 +23,12 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Type;
 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;
+import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class DefaultServiceRegistryTest {
@@ -38,10 +38,10 @@ public class DefaultServiceRegistryTest {
     @Test
     public void throwsExceptionForUnknownService() {
         try {
-            registry.get(Map.class);
+            registry.get(StringBuilder.class);
             fail();
         } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+            assertThat(e.getMessage(), equalTo("No service of type StringBuilder available in TestRegistry."));
         }
     }
 
@@ -65,15 +65,15 @@ public class DefaultServiceRegistryTest {
         TestRegistry registry = new TestRegistry(parent);
 
         context.checking(new Expectations(){{
-            one(parent).get(Map.class);
-            will(throwException(new UnknownServiceException(Map.class, "fail")));
+            one(parent).get(StringBuilder.class);
+            will(throwException(new UnknownServiceException(StringBuilder.class, "fail")));
         }});
 
         try {
-            registry.get(Map.class);
+            registry.get(StringBuilder.class);
             fail();
         } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+            assertThat(e.getMessage(), equalTo("No service of type StringBuilder available in TestRegistry."));
         }
     }
 
@@ -123,6 +123,16 @@ public class DefaultServiceRegistryTest {
     }
 
     @Test
+    public void failsWhenArrayClassRequested() {
+        try {
+            registry.get(String[].class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Cannot locate service of array type String[]."));
+        }
+    }
+
+    @Test
     public void usesDecoratorMethodToDecorateParentServiceInstance() {
         final ServiceRegistry parent = context.mock(ServiceRegistry.class);
         ServiceRegistry registry = new RegistryWithDecoratorMethods(parent);
@@ -163,6 +173,39 @@ public class DefaultServiceRegistryTest {
     }
 
     @Test
+    public void canGetAFactoryUsingParameterisedFactoryType() throws NoSuchFieldException {
+        ServiceRegistry registry = new RegistryWithMultipleFactoryMethods();
+
+        Factory<String> stringFactory = (Factory<String>) registry.get(getStringFactoryType());
+        assertEquals(stringFactory.create(), "hello");
+
+        Factory<Number> numberFactory = (Factory<Number>) registry.get(getNumberFactoryType());
+        assertEquals(numberFactory.create(), 12);
+    }
+
+    @Test
+    public void canGetAFactoryUsingFactoryTypeWithBounds() throws NoSuchFieldException {
+        Factory<? super BigDecimal> superBigDecimalFactory = (Factory<? super BigDecimal>) registry.get(getSuperBigDecimalFactoryType());
+        assertEquals(superBigDecimalFactory.create(), BigDecimal.valueOf(0));
+
+        Factory<? extends BigDecimal> extendsBigDecimalFactory = (Factory<? extends BigDecimal>) registry.get(getExtendsBigDecimalFactoryType());
+        assertEquals(extendsBigDecimalFactory.create(), BigDecimal.valueOf(1));
+
+        Factory<? extends Number> extendsNumberFactory = (Factory<? extends Number>) registry.get(getExtendsNumberFactoryType());
+        assertEquals(extendsNumberFactory.create(), BigDecimal.valueOf(2));
+    }
+
+    @Test
+    public void cannotGetAFactoryUsingRawFactoryType() {
+        try {
+            registry.get(Factory.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Cannot locate service of raw type Factory. Use getFactory() or get(Type) instead."));
+        }
+    }
+
+    @Test
     public void usesAFactoryServiceToCreateInstances() {
         assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(0)));
         assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(1)));
@@ -397,6 +440,32 @@ public class DefaultServiceRegistryTest {
         }
     }
 
+    private Factory<Number> numberFactory;
+    private Factory<String> stringFactory;
+    private Factory<? super BigDecimal> superBigDecimalFactory;
+    private Factory<? extends BigDecimal> extendsBigDecimalFactory;
+    private Factory<? extends Number> extendsNumberFactory;
+
+    private Type getNumberFactoryType() throws NoSuchFieldException {
+        return getClass().getDeclaredField("numberFactory").getGenericType();
+    }
+
+    private Type getStringFactoryType() throws NoSuchFieldException {
+        return getClass().getDeclaredField("stringFactory").getGenericType();
+    }
+
+    private Type getSuperBigDecimalFactoryType() throws NoSuchFieldException {
+        return getClass().getDeclaredField("superBigDecimalFactory").getGenericType();
+    }
+
+    private Type getExtendsBigDecimalFactoryType() throws NoSuchFieldException {
+        return getClass().getDeclaredField("extendsBigDecimalFactory").getGenericType();
+    }
+
+    private Type getExtendsNumberFactoryType() throws NoSuchFieldException {
+        return getClass().getDeclaredField("extendsNumberFactory").getGenericType();
+    }
+
     private static class TestRegistry extends DefaultServiceRegistry {
         public TestRegistry() {
         }
@@ -468,6 +537,24 @@ public class DefaultServiceRegistryTest {
         }
     }
 
+    private static class RegistryWithMultipleFactoryMethods extends DefaultServiceRegistry {
+        Factory<Number> createObjectFactory() {
+            return new Factory<Number>() {
+                public Number create() {
+                    return 12;
+                }
+            };
+        }
+
+        Factory<String> createStringFactory() {
+            return new Factory<String>() {
+                public String create() {
+                    return "hello";
+                }
+            };
+        }
+    }
+
     private static class TestFactory implements Factory<BigDecimal> {
         int value;
         public BigDecimal create() {
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
new file mode 100644
index 0000000..472c548
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/util/CollectionUtilsTest.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.util
+
+import org.gradle.api.Action
+import org.gradle.api.Transformer
+import org.gradle.api.internal.ClosureBackedAction
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.util.CollectionUtils.*
+
+ at Unroll
+class CollectionUtilsTest extends Specification {
+
+    def "list filtering"() {
+        given:
+        def spec = Specs.convertClosureToSpec { it < 5 }
+        def filter = { Integer[] nums -> 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 -> 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 -> 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 "map filtering"() {
+        expect:
+        def filtered = filter(a: 1, b: 2, c: 3, spec { it.value < 2 })
+        filtered.size() == 1
+        filtered.a == 1
+    }
+
+    def toStringList() {
+        def list = [42, "string"]
+
+        expect:
+        toStringList([]) == []
+        toStringList(list) == ["42", "string"]
+    }
+
+    def "list compacting"() {
+        expect:
+        compact([1, null, 2]) == [1, 2]
+        compact([null, 1, 2]) == [1, 2]
+        compact([1, 2, null]) == [1, 2]
+
+        def l = [1, 2, 3]
+        compact(l).is l
+
+    }
+
+    def "collect array"() {
+        expect:
+        collect([1, 2, 3] as Object[], transformer { it * 2 }) == [2, 4, 6]
+    }
+
+    def "list stringize"() {
+        expect:
+        stringize([1, 2, 3]) == ["1", "2", "3"]
+        stringize([]) == []
+    }
+
+    def "stringize"() {
+        expect:
+        stringize(["c", "b", "a"], new TreeSet<String>()) == ["a", "b", "c"] as Set
+    }
+
+    def "replacing"() {
+        given:
+        def l = [1, 2, 3]
+
+        expect:
+        replace l, spec { it == 2 }, transformer { 2 * 2 }
+        l == [1, 4, 3]
+
+        replace l, spec { it > 1 }, transformer { 0 }
+        l == [1, 0, 0]
+
+        !replace(l, spec { false }, transformer { it })
+    }
+
+    @Unroll
+    "diffing sets"() {
+        given:
+        def leftSet = left as Set
+        def rightSet = right as Set
+        def leftOnlySet = leftOnly as Set
+        def rightOnlySet = rightOnly as Set
+
+        when:
+        def diff = diffSetsBy(leftSet, rightSet, transformer { it + 10 })
+
+        then:
+        diff.leftOnly == leftOnlySet
+        diff.common.size() == common.size()
+        if (common) {
+            common.each { inCommon ->
+                diff.common.find { it.left == inCommon && it.right == inCommon }
+            }
+        }
+        diff.rightOnly == rightOnlySet
+
+        where:
+        left      | right     | leftOnly  | rightOnly | common
+        [1, 2, 3] | [2, 3, 4] | [1]       | [4]       | [2, 3]
+        []        | []        | []        | []        | []
+        [1, 2, 3] | []        | [1, 2, 3] | []        | []
+        []        | [1, 2, 3] | []        | [1, 2, 3] | []
+        [1, 2, 3] | [1, 2, 3] | []        | []        | [1, 2, 3]
+    }
+
+    def "collect as map"() {
+        expect:
+        collectMap([1, 2, 3], transformer { it * 10 }) == [10: 1, 20: 2, 30: 3]
+        collectMap([], transformer { it * 10 }) == [:]
+    }
+
+    def "every"() {
+        expect:
+        every([1, 2, 3], spec { it < 4 })
+        !every([1, 2, 4], spec { it < 4 })
+        !every([1], spec { it instanceof String })
+        every([], spec { false })
+    }
+
+    def "flattenToList"() {
+        given:
+        def integers = [1, 2, 3]
+
+        expect:
+        flattenToList([1, 2, 3] as Set) == [1, 2, 3]
+        flattenToList("asdfa") == ["asdfa"]
+        flattenToList(null) == [null]
+        flattenToList([null, [null, null]]) == [null, null, null]
+        flattenToList(integers) == integers
+        flattenToList([1, 2, 3] as Set) == [1, 2, 3]
+        flattenToList([] as Set) == []
+
+        when:
+        flattenToList(Map, "foo")
+
+        then:
+        thrown(ClassCastException)
+
+        when:
+        flattenToList(Map, [[a: 1], "foo"])
+
+        then:
+        thrown(ClassCastException)
+
+        and:
+        flattenToList(Number, 1, [2, 3]) == [1, 2, 3]
+    }
+
+    def "joining"() {
+        expect:
+        join(",", [1, 2, 3]) == "1,2,3"
+        join(",", [1]) == "1"
+        join(",", []) == ""
+
+        and:
+        join(",", [1, 2, 3].toArray()) == "1,2,3"
+        join(",", [1].toArray()) == "1"
+        join(",", [].toArray()) == ""
+    }
+
+    def "joining with nulls"() {
+        when:
+        join(separator, objects)
+
+        then:
+        def e = thrown(NullPointerException)
+        e.message == "The '$param' cannot be null"
+
+        where:
+        separator | objects            | param
+        null      | [] as Object[]     | "separator"
+        ""        | null as Object[]   | "objects"
+        null      | []                 | "separator"
+        ""        | null as Collection | "objects"
+        null      | null as Collection | "separator"
+        null      | null as Object[]   | "separator"
+    }
+
+    def "addAll"() {
+        expect:
+        addAll([], [1, 2, 3] as Iterable) == [1, 2, 3]
+        addAll([] as Set, [1, 2, 3, 1] as Iterable) == [1, 2, 3] as Set
+    }
+
+    def "injection"() {
+        expect:
+        def target = []
+        def result = inject(target, [1, 2, 3], action { it.target.add(it.item.toString()) })
+        result.is(target)
+        result == ["1", "2", "3"]
+
+        inject([], [[1, 2], [3]], action { it.target.addAll(it.item) }) == [1, 2, 3]
+
+        when:
+        inject(null, [], action {})
+
+        then:
+        def e = thrown(NullPointerException)
+        e.message == "The 'target' cannot be null"
+
+        when:
+        inject([], null, action {})
+
+        then:
+        e = thrown(NullPointerException)
+        e.message == "The 'items' cannot be null"
+
+        when:
+        inject([], [], null)
+
+        then:
+        e = thrown(NullPointerException)
+        e.message == "The 'action' cannot be null"
+    }
+
+    Spec<?> spec(Closure c) {
+        Specs.convertClosureToSpec(c)
+    }
+
+    Transformer<?, ?> transformer(Closure c) {
+        c as Transformer
+    }
+
+    Action action(Closure c) {
+        new ClosureBackedAction(c)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/build-comparison.gradle b/subprojects/build-comparison/build-comparison.gradle
new file mode 100644
index 0000000..7c45cdf
--- /dev/null
+++ b/subprojects/build-comparison/build-comparison.gradle
@@ -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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+
+    compile project(":core")
+    compile project(":toolingApi")
+    compile project(":reporting")
+    compile project(":ide") // for FileOutcomeIdentifier enum
+    compile libraries.guava
+    compile libraries.slf4j_api
+
+    testCompile "org.jsoup:jsoup:1.6.3"
+}
+
+processResources {
+    into "org/gradle/api/plugins/buildcomparison/render/internal/html", {
+        from { project(":docs").css }
+        include "base.css"
+    }
+}
+
+useTestFixtures()
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
new file mode 100644
index 0000000..e25c10e
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.gradle.util.TestFile
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import org.junit.Rule
+
+class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
+    private static final String NOT_IDENTICAL_MESSAGE_PREFIX = "The build outcomes were not found to be identical. See the report at: file:///"
+    @Rule TestResources testResources
+
+    @Override
+    String getPluginId() {
+        "compare-gradle-builds"
+    }
+
+    @Override
+    String getMainTask() {
+        "help"
+    }
+
+    def setup() {
+        executer.withForkingExecuter()
+        applyPlugin()
+    }
+
+    def compareArchives() {
+        given:
+        buildFile << """
+            compareGradleBuilds {
+                sourceBuild.projectDir "source"
+                targetBuild { projectDir "target" }
+            }
+        """
+
+        when:
+        fails "compareGradleBuilds"
+
+        then:
+        failedBecauseNotIdentical()
+
+        def html = html()
+
+        // Name of outcome
+        html.select("h3").text() == ":jar"
+
+        // Entry comparisons
+        def rows = html.select("table")[2].select("tr").tail().collectEntries { [it.select("td")[0].text(), it.select("td")[1].text()] }
+        rows.size() == 4
+        rows["org/gradle/Changed.class"] == "entry in the source build is 394 bytes - in the target build it is 471 bytes (+77)"
+        rows["org/gradle/DifferentCrc.class"] == "entries are of identical size but have different content"
+        rows["org/gradle/SourceBuildOnly.class"] == "entry does not exist in target build archive"
+        rows["org/gradle/TargetBuildOnly.class"] == "entry does not exist in source build archive"
+
+        and:
+        storedFile("source").exists()
+        storedFile("source/_jar").list().toList() == ["testBuild.jar"]
+        storedFile("target/_jar").list().toList() == ["testBuild.jar"]
+
+        and: // old filestore not around
+        !testDir.list().any { it.startsWith(CompareGradleBuilds.TMP_FILESTORAGE_PREFIX) }
+    }
+
+    void failedBecauseNotIdentical() {
+        failure.assertHasCause(NOT_IDENTICAL_MESSAGE_PREFIX)
+    }
+
+    def "compare same project"() {
+        given:
+        buildFile << """
+            apply plugin: "java"
+        """
+
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        when:
+        run "compareGradleBuilds"
+
+        then:
+        html().select("p").last().text() == "The archives are completely identical."
+        output.contains("The source build and target build are identical")
+    }
+
+    def "compare project with unknown outcomes"() {
+        given:
+        file("file.txt") << "text"
+        buildFile << """
+            apply plugin: "java-base"
+
+            configurations {
+                archives
+            }
+
+            task tarArchive(type: Tar) {
+                from "file.txt"
+            }
+
+            artifacts {
+                archives tarArchive
+            }
+        """
+
+        when:
+        fails "compareGradleBuilds"
+
+        then:
+        failedBecauseNotIdentical()
+
+        html().select("h3")[0].nextSibling().nextSibling().text() == "This version of Gradle does not understand this kind of build outcome."
+    }
+
+    def "cannot compare when both sides are old"() {
+        when:
+        buildFile << """
+            compareGradleBuilds {
+                sourceBuild.gradleVersion "1.1"
+                targetBuild.gradleVersion "1.1"
+            }
+        """
+
+        then:
+        fails "compareGradleBuilds"
+
+        and:
+        failure.assertHasCause("Cannot run comparison because both the source and target build are to be executed with a Gradle version older than Gradle 1.2 (source: 1.1, target: 1.1).")
+    }
+
+    def "cannot compare when source is older than 1.0"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            compareGradleBuilds {
+                sourceBuild.gradleVersion "1.0-rc-1"
+            }
+        """
+
+        then:
+        fails "compareGradleBuilds"
+
+        and:
+        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: 1.0-rc-1, target: ${distribution.version})")
+    }
+
+    def "can ignore errors"() {
+        given:
+        file("file.txt") << "text"
+        buildFile << """
+            apply plugin: "java-base"
+
+            configurations {
+                archives
+            }
+
+            task tarArchive(type: Tar) {
+                from "file.txt"
+            }
+
+            artifacts {
+                archives tarArchive
+            }
+
+            compareGradleBuilds.ignoreFailures true
+        """
+
+        when:
+        succeeds "compareGradleBuilds"
+
+        then:
+        output.contains(NOT_IDENTICAL_MESSAGE_PREFIX)
+    }
+
+    def "cannot compare when target is older than 1.0"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            compareGradleBuilds {
+                targetBuild.gradleVersion "1.0-rc-1"
+            }
+        """
+
+        then:
+        fails "compareGradleBuilds"
+
+        and:
+        failure.assertHasCause("Builds must be executed with Gradle 1.0 or newer (source: ${distribution.version}, target: 1.0-rc-1)")
+    }
+
+    def "can handle artifact not existing on source side"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            compareGradleBuilds {
+                sourceBuild.arguments = ["-PnoJar"]
+            }
+
+            if (project.hasProperty("noJar")) {
+                jar.enabled = false
+            }
+        """
+
+        then:
+        fails "compareGradleBuilds"
+
+        and:
+        comparisonResultMsg(html(), ":jar") == "The archive was only produced by the target build."
+    }
+
+    def "can handle artifact not existing on target side"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            compareGradleBuilds {
+                targetBuild.arguments = ["-PnoJar"]
+            }
+
+            if (project.hasProperty("noJar")) {
+                jar.enabled = false
+            }
+        """
+
+        then:
+        fails "compareGradleBuilds"
+
+        and:
+        comparisonResultMsg(html(), ":jar") == "The archive was only produced by the source build."
+    }
+
+    def "can handle uncompared outcomes"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            compareGradleBuilds {
+                sourceBuild.arguments = ["-PdoJavadoc"]
+                targetBuild.arguments = ["-PdoSource"]
+
+            }
+
+            if (project.hasProperty("doSource")) {
+                task sourceJar(type: Jar) {
+                    from sourceSets.main.allJava
+                    classifier "source"
+                }
+                artifacts {
+                    archives sourceJar
+                }
+            }
+
+            if (project.hasProperty("doJavadoc")) {
+                task javadocJar(type: Jar) {
+                    from tasks.javadoc
+                    classifier "javadoc"
+                }
+                artifacts {
+                    archives javadocJar
+                }
+            }
+        """
+
+        and:
+        file("src/main/java/Thing.java") << "public class Thing {}"
+
+        then:
+        fails "compareGradleBuilds"
+
+        def html = html()
+        html.select("h2").find { it.text() == "Uncompared source outcomes" }
+        html.select("h2").find { it.text() == "Uncompared target outcomes" }
+
+        html.select(".build-outcome.source h3").text() == ":javadocJar"
+        html.select(".build-outcome.target h3").text() == ":sourceJar"
+    }
+
+    Document html(path = "build/reports/compareGradleBuilds/index.html") {
+        Jsoup.parse(file(path), "utf8")
+    }
+
+    TestFile storedFile(String path, String base = "build/reports/compareGradleBuilds/files") {
+        file("$base/$path")
+    }
+
+    String comparisonResultMsg(Document html, String id) {
+        outcomeComparison(html, id).select(".comparison-result-msg").text()
+    }
+
+    Element outcomeComparison(Document html, String id) {
+        html.select("div.build-outcome-comparison").find { it.id() == id }
+    }
+}
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
new file mode 100644
index 0000000..07b0f6c
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleExecuter
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.util.TestFile
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+ at TargetVersions(["1.0", "1.1"])
+class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSpec {
+
+    ExecutionResult result
+
+    void applyPlugin(TestFile file = buildFile) {
+        versionGuard(file) { "apply plugin: 'compare-gradle-builds'" }
+    }
+
+    def "can compare identical builds with source pre 1.2"() {
+        given:
+        applyPlugin()
+        buildFile << "apply plugin: 'java'"
+        versionGuard { """
+            compareGradleBuilds {
+                sourceBuild.gradleVersion "${previous.version}"
+            }
+        """ }
+
+        and:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        when:
+        runComparisonWithCurrent()
+        sourceWasInferred()
+
+        then:
+        sourceBuildVersion == previous.version
+        targetBuildVersion == current.version
+    }
+
+    def "can compare identical builds with target pre 1.2"() {
+        given:
+        applyPlugin()
+        buildFile << "apply plugin: 'java'"
+        versionGuard { """
+            compareGradleBuilds {
+                targetBuild.gradleVersion "${previous.version}"
+            }
+        """ }
+
+        and:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        when:
+        runComparisonWithCurrent()
+        targetWasInferred()
+
+        then:
+        sourceBuildVersion == current.version
+        targetBuildVersion == previous.version
+    }
+
+    def "can compare different builds with source pre 1.2"() {
+        given:
+        applyPlugin()
+        buildFile << "apply plugin: 'java'"
+        versionGuard { """
+            compareGradleBuilds {
+                sourceBuild.gradleVersion "${previous.version}"
+            }
+
+            compileJava { options.debug = !options.debug }
+        """ }
+
+        and:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        when:
+        failBecauseNotIdentical()
+        sourceWasInferred()
+
+        then:
+        sourceBuildVersion == previous.version
+        targetBuildVersion == current.version
+    }
+
+    def "can compare different builds with target pre 1.2"() {
+        given:
+        applyPlugin()
+        buildFile << "apply plugin: 'java'"
+        versionGuard { """
+            compareGradleBuilds {
+                targetBuild.gradleVersion "${previous.version}"
+            }
+
+            compileJava { options.debug = !options.debug }
+        """ }
+
+        and:
+        file("src/main/java/Thing.java") << "class Thing {}"
+
+        when:
+        failBecauseNotIdentical()
+        targetWasInferred()
+
+        then:
+        sourceBuildVersion == current.version
+        targetBuildVersion == previous.version
+    }
+
+    protected versionGuard(TestFile file = buildFile, Closure string) {
+        file << "\nif (GradleVersion.current().version == '${current.version}') {\n"
+        file << string()
+        file << "\n}\n"
+    }
+
+    protected ExecutionResult runComparisonWithCurrent() {
+        result = currentExecuter().run()
+        result
+    }
+
+    protected GradleExecuter currentExecuter() {
+        current.executer().withForkingExecuter().withTasks("compareGradleBuilds")
+    }
+
+    Document html(path = "build/reports/compareGradleBuilds/index.html") {
+        Jsoup.parse(file(path), "utf8")
+    }
+
+    String getSourceBuildVersion(Document html = this.html()) {
+        html.body().select("table")[0].select("tr")[2].select("td")[0].text()
+    }
+
+    String getTargetBuildVersion(Document html = this.html()) {
+        html.body().select("table")[0].select("tr")[2].select("td")[1].text()
+    }
+
+    void failBecauseNotIdentical() {
+        result = currentExecuter().runWithFailure()
+        result.assertHasCause("The build outcomes were not found to be identical. See the report at: file:///")
+    }
+
+    void sourceWasInferred(Document html = this.html()) {
+        hasInferredLogWarning("source")
+        hasInferredHtmlWarning("source", html)
+    }
+
+    void targetWasInferred(Document html = this.html()) {
+        hasInferredLogWarning("target")
+        hasInferredHtmlWarning("target", html)
+    }
+
+    void hasInferredLogWarning(String buildName) {
+        assert result.output.contains("The build outcomes for the $buildName build will be inferred from the")
+    }
+
+    void hasInferredHtmlWarning(String buildName, Document html) {
+        assert html.body().select(".warning.inferred-outcomes").text().contains("Build outcomes were not able to be determined for the $buildName build")
+    }
+
+}
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/build.gradle b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/build.gradle
new file mode 100644
index 0000000..405a81a
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/build.gradle
@@ -0,0 +1 @@
+apply plugin: "java"
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/settings.gradle b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/settings.gradle
new file mode 100644
index 0000000..1affce5
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = "testBuild"
+
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Changed.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Changed.java
new file mode 100644
index 0000000..54c2e0e
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Changed.java
@@ -0,0 +1,8 @@
+package org.gradle;
+
+public class Changed {
+    private String field1;
+    private int field2;
+
+    public void method1() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/DifferentCrc.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/DifferentCrc.java
new file mode 100644
index 0000000..52d0e6f
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/DifferentCrc.java
@@ -0,0 +1,8 @@
+package org.gradle;
+
+public class DifferentCrc {
+    private String field1;
+    private int field2;
+
+    public void method1() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/SourceBuildOnly.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/SourceBuildOnly.java
new file mode 100644
index 0000000..d5fd197
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/SourceBuildOnly.java
@@ -0,0 +1,3 @@
+package org.gradle;
+
+public class SourceBuildOnly {}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Unchanged.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Unchanged.java
new file mode 100644
index 0000000..c8c7d00
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/source/src/main/java/org/gradle/Unchanged.java
@@ -0,0 +1,8 @@
+package org.gradle;
+
+public class Unchanged {
+    private String field1;
+    private int field2;
+
+    public void method1() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/build.gradle b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/build.gradle
new file mode 100644
index 0000000..ff1a2fb
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/build.gradle
@@ -0,0 +1,2 @@
+apply plugin: "java"
+
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/settings.gradle b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/settings.gradle
new file mode 100644
index 0000000..1affce5
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = "testBuild"
+
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Changed.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Changed.java
new file mode 100644
index 0000000..6470296
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Changed.java
@@ -0,0 +1,10 @@
+package org.gradle;
+
+public class Changed {
+    private String changedField;
+    private int field2;
+
+    public void method1() {}
+
+    public void addedMethod() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/DifferentCrc.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/DifferentCrc.java
new file mode 100644
index 0000000..71206d6
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/DifferentCrc.java
@@ -0,0 +1,8 @@
+package org.gradle;
+
+public class DifferentCrc {
+    private String field9;
+    private int field2;
+
+    public void method1() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/TargetBuildOnly.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/TargetBuildOnly.java
new file mode 100644
index 0000000..f9ac9e8
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/TargetBuildOnly.java
@@ -0,0 +1,3 @@
+package org.gradle;
+
+public class TargetBuildOnly {}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Unchanged.java b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Unchanged.java
new file mode 100644
index 0000000..c8c7d00
--- /dev/null
+++ b/subprojects/build-comparison/src/integTest/resources/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec/compareArchives/target/src/main/java/org/gradle/Unchanged.java
@@ -0,0 +1,8 @@
+package org.gradle;
+
+public class Unchanged {
+    private String field1;
+    private int field2;
+
+    public void method1() {}
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparator.java
new file mode 100644
index 0000000..5e84755
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparator.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.plugins.buildcomparison.compare.internal;
+
+/**
+ * An object that can compare builds, according to a specification.
+ */
+public interface BuildComparator {
+
+    /**
+     * Performs the comparison, according to the given specification.
+     *
+     * @param spec The specification of the comparison.
+     * @return The result of the comparison. Never null.
+     */
+    BuildComparisonResult compareBuilds(BuildComparisonSpec spec);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonResult.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonResult.java
new file mode 100644
index 0000000..412b7a0
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonResult.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The result of a comparison between two sets of build outcomes.
+ */
+public class BuildComparisonResult {
+
+    private List<BuildOutcomeComparisonResult<?>> comparisons;
+    private Set<BuildOutcome> uncomparedSourceOutcomes;
+    private Set<BuildOutcome> uncomparedTargetOutcomes;
+
+
+    public BuildComparisonResult(Set<BuildOutcome> uncomparedSourceOutcomes, Set<BuildOutcome> uncomparedTargetOutcomes, List<BuildOutcomeComparisonResult<?>> comparisons) {
+        this.comparisons = comparisons;
+        this.uncomparedSourceOutcomes = uncomparedSourceOutcomes;
+        this.uncomparedTargetOutcomes = uncomparedTargetOutcomes;
+    }
+
+    public List<BuildOutcomeComparisonResult<?>> getComparisons() {
+        return comparisons;
+    }
+
+    public Set<BuildOutcome> getUncomparedSourceOutcomes() {
+        return uncomparedSourceOutcomes;
+    }
+
+    public Set<BuildOutcome> getUncomparedTargetOutcomes() {
+        return uncomparedTargetOutcomes;
+    }
+
+    public boolean isBuildsAreIdentical() {
+        //noinspection SimplifiableIfStatement
+        if (!getUncomparedSourceOutcomes().isEmpty() || !getUncomparedTargetOutcomes().isEmpty()) {
+            return false;
+        } else {
+            return CollectionUtils.every(comparisons, new Spec<BuildOutcomeComparisonResult<?>>() {
+                public boolean isSatisfiedBy(BuildOutcomeComparisonResult<?> comparisonResult) {
+                    return comparisonResult.isOutcomesAreIdentical();
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpec.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpec.java
new file mode 100644
index 0000000..ab50b7b
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpec.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A specification of how two sets of build outcomes are to be compared.
+ *
+ * Instances should be read only.
+ */
+public interface BuildComparisonSpec {
+
+    /**
+     * The complete list of outcomes from the from side.
+     *
+     * The returned collection is always immutable.
+     *
+     * @return The complete (immutable) list of outcomes from the from side. Never null.
+     */
+    Set<BuildOutcome> getSource();
+
+    /**
+     * The complete list of outcomes from the to side.
+     *
+     *
+     * @return The complete (immutable) list of outcomes from the to side. Never null.
+     */
+    Set<BuildOutcome> getTarget();
+
+    /**
+     * The association between build outcomes from both sides.
+     *
+     * The outcomes should be compared in the order of the list.
+     * <p>
+     * Not all of the outcomes in the from and to sets will necessarily need be associated.
+     *
+     * @return The (immutable) list of associated build outcomes.
+     */
+    List<BuildOutcomeAssociation<?>> getOutcomeAssociations();
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecBuilder.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecBuilder.java
new file mode 100644
index 0000000..f8d417c
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecBuilder.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.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+/**
+ * Builder for build comparison specifications.
+ */
+public interface BuildComparisonSpecBuilder {
+
+    <A extends BuildOutcome, F extends A, T extends A> BuildOutcomeAssociation<A> associate(F from, T to, Class<A> type);
+
+    <F extends BuildOutcome> void addUnassociatedFrom(F from);
+
+    <T extends BuildOutcome> void addUnassociatedTo(T to);
+
+    BuildComparisonSpec build();
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactory.java
new file mode 100644
index 0000000..c77c27f
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociator;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class BuildComparisonSpecFactory {
+
+    private final BuildOutcomeAssociator associator;
+
+    public BuildComparisonSpecFactory(BuildOutcomeAssociator associator) {
+        this.associator = associator;
+    }
+
+    public BuildComparisonSpec createSpec(Set<BuildOutcome> from, Set<BuildOutcome> to) {
+        BuildComparisonSpecBuilder builder = new DefaultBuildComparisonSpecBuilder();
+
+        Set<BuildOutcome> toCopy = new HashSet<BuildOutcome>(to);
+
+        for (BuildOutcome fromBuildOutcome : from) {
+            BuildOutcome toBuildOutcome = null;
+            Class<? extends BuildOutcome> associationType = null;
+
+            for (BuildOutcome buildOutcome : toCopy) {
+                toBuildOutcome = buildOutcome;
+                associationType = associator.findAssociationType(fromBuildOutcome, toBuildOutcome);
+
+                if (associationType != null) {
+                    break;
+                }
+            }
+
+            if (associationType == null) {
+                builder.addUnassociatedFrom(fromBuildOutcome);
+            } else {
+                builder.associate(associationType.cast(fromBuildOutcome), associationType.cast(toBuildOutcome), (Class<BuildOutcome>)associationType);
+                toCopy.remove(toBuildOutcome);
+            }
+        }
+
+        for (BuildOutcome buildOutcome : toCopy) {
+            builder.addUnassociatedTo(buildOutcome);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparator.java
new file mode 100644
index 0000000..39baeca
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparator.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.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+/**
+ * An object that can compare two build outcomes of a common type.
+ *
+ * @param <T> The common type of the outcomes to be compared.
+ * @param <R> The type of the result returned by the comparison.
+ */
+public interface BuildOutcomeComparator<T extends BuildOutcome, R extends BuildOutcomeComparisonResult<T>> {
+
+    /**
+     * The type of outcomes that this comparator can compare.
+     *
+     * @return The type of outcomes that this comparator can compare. Never null.
+     */
+    Class<T> getComparedType();
+
+    /**
+     * Compares the associated outcomes.
+     *
+     * @param association The associated outcomes to compare.
+     * @return The result of the comparison. Never null.
+     */
+    R compare(BuildOutcomeAssociation<T> association);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparatorFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparatorFactory.java
new file mode 100644
index 0000000..92b63a0
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparatorFactory.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+public interface BuildOutcomeComparatorFactory {
+
+    <T extends BuildOutcome> BuildOutcomeComparator<T, ?> getComparator(Class<T> outcomeType);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResult.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResult.java
new file mode 100644
index 0000000..52accd8
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResult.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.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+/**
+ * The result of a comparison between to associated build outcomes.
+ *
+ * Implementors exposes information about how the outcomes were different.
+ *
+ * @param <T> The type of outcome that was compared.
+ */
+public interface BuildOutcomeComparisonResult<T extends BuildOutcome> {
+
+    /**
+     * The associated outcomes that were compared.
+     *
+     * @return The associated outcomes that were compared. Never null
+     */
+    BuildOutcomeAssociation<T> getCompared();
+
+    /**
+     * True if the outcomes are considered to be strictly identical, otherwise false.
+     *
+     * @return True if the outcomes are considered to be strictly identical, otherwise false.
+     */
+    boolean isOutcomesAreIdentical();
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResultSupport.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResultSupport.java
new file mode 100644
index 0000000..c8d5a0d
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildOutcomeComparisonResultSupport.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.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+/**
+ * Convenience base class for BuildOutcomeComparisonResult implementations.
+ *
+ * @param <T> The type of the outcome the result is for.
+ */
+public abstract class BuildOutcomeComparisonResultSupport<T extends BuildOutcome> implements BuildOutcomeComparisonResult<T> {
+
+    private final BuildOutcomeAssociation<T> compared;
+
+    protected BuildOutcomeComparisonResultSupport(BuildOutcomeAssociation<T> compared) {
+        this.compared = compared;
+    }
+
+    public BuildOutcomeAssociation<T> getCompared() {
+        return compared;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/ComparisonResultType.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/ComparisonResultType.java
new file mode 100644
index 0000000..79e187a
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/ComparisonResultType.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.plugins.buildcomparison.compare.internal;
+
+public enum ComparisonResultType {
+    EQUAL,
+    UNEQUAL,
+    SOURCE_ONLY,
+    TARGET_ONLY,
+    NON_EXISTENT; // doesn't exist on either side, nothing to compare
+
+    public void throwUnsupported() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException(String.format("Support for result type '%s' has not been implemented in this context", toString()));
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparator.java
new file mode 100644
index 0000000..fec8441
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparator.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultBuildComparator implements BuildComparator {
+
+    BuildOutcomeComparatorFactory comparatorFactory;
+
+    public DefaultBuildComparator(BuildOutcomeComparatorFactory comparatorFactory) {
+        this.comparatorFactory = comparatorFactory;
+    }
+
+    public BuildComparisonResult compareBuilds(BuildComparisonSpec spec) {
+        Set<BuildOutcome> uncomparedFrom = new HashSet<BuildOutcome>(spec.getSource());
+        Set<BuildOutcome> uncomparedTo = new HashSet<BuildOutcome>(spec.getTarget());
+
+        Set<BuildOutcome> unknownFrom = new HashSet<BuildOutcome>();
+        Set<BuildOutcome> unknownTo = new HashSet<BuildOutcome>();
+
+        List<BuildOutcomeComparisonResult<?>> results = new LinkedList<BuildOutcomeComparisonResult<?>>();
+
+        for (BuildOutcomeAssociation<? extends BuildOutcome> outcomeAssociation : spec.getOutcomeAssociations()) {
+            BuildOutcome from = outcomeAssociation.getSource();
+            boolean unknown = false;
+
+            if (!uncomparedFrom.remove(from)) {
+                unknown = true;
+                unknownFrom.add(from);
+            }
+
+            BuildOutcome to = outcomeAssociation.getTarget();
+            if (!uncomparedTo.remove(to)) {
+                unknown = true;
+                unknownTo.add(to);
+            }
+
+            // TODO - error if there are unknowns?
+            if (!unknown) {
+                BuildOutcomeComparator<?, ?> comparator = comparatorFactory.getComparator(outcomeAssociation.getType());
+                if (comparator == null) {
+                    // TODO - better exception
+                    throw new RuntimeException(String.format("No comparator for %s", outcomeAssociation.getType()));
+                }
+
+                @SuppressWarnings("unchecked")
+                BuildOutcomeComparisonResult<?> comparisonResult = comparator.compare((BuildOutcomeAssociation) outcomeAssociation);
+
+                results.add(comparisonResult);
+            }
+        }
+
+        return new BuildComparisonResult(uncomparedFrom, uncomparedTo, results);
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpec.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpec.java
new file mode 100644
index 0000000..6b5e208
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpec.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.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+import java.util.*;
+
+public class DefaultBuildComparisonSpec implements BuildComparisonSpec {
+
+    private final Set<BuildOutcome> source;
+    private final Set<BuildOutcome> target;
+    private final List<BuildOutcomeAssociation<?>> outcomeAssociations;
+
+    public DefaultBuildComparisonSpec(Set<BuildOutcome> source, Set<BuildOutcome> target, List<BuildOutcomeAssociation<?>> outcomeAssociations) {
+        this.source = Collections.unmodifiableSet(new HashSet<BuildOutcome>(source));
+        this.target = Collections.unmodifiableSet(new HashSet<BuildOutcome>(target));
+        this.outcomeAssociations = Collections.unmodifiableList(new ArrayList<BuildOutcomeAssociation<?>>(outcomeAssociations));
+    }
+
+    public Set<BuildOutcome> getSource() {
+        return source;
+    }
+
+    public Set<BuildOutcome> getTarget() {
+        return target;
+    }
+
+    public List<BuildOutcomeAssociation<?>> getOutcomeAssociations() {
+        return outcomeAssociations;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpecBuilder.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpecBuilder.java
new file mode 100644
index 0000000..074a48f
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparisonSpecBuilder.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.DefaultBuildOutcomeAssociation;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultBuildComparisonSpecBuilder implements BuildComparisonSpecBuilder {
+
+    private final Set<BuildOutcome> source = new HashSet<BuildOutcome>();
+    private final Set<BuildOutcome> target = new HashSet<BuildOutcome>();
+    private final List<BuildOutcomeAssociation<?>> outcomeAssociations = new LinkedList<BuildOutcomeAssociation<?>>();
+
+    public <A extends BuildOutcome, F extends A, T extends A> BuildOutcomeAssociation<A> associate(F from, T to, Class<A> type) {
+        this.source.add(from);
+        this.target.add(to);
+
+        BuildOutcomeAssociation<A> outcomeAssociation = new DefaultBuildOutcomeAssociation<A>(from, to, type);
+        outcomeAssociations.add(outcomeAssociation);
+
+        return outcomeAssociation;
+    }
+
+    public <F extends BuildOutcome> void addUnassociatedFrom(F from) {
+        this.source.add(from);
+    }
+
+    public <T extends BuildOutcome> void addUnassociatedTo(T to) {
+        this.target.add(to);
+    }
+
+    public BuildComparisonSpec build() {
+        return new DefaultBuildComparisonSpec(source, target, outcomeAssociations);
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactory.java
new file mode 100644
index 0000000..3847458
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactory.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.api.plugins.buildcomparison.compare.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultBuildOutcomeComparatorFactory implements BuildOutcomeComparatorFactory {
+
+    Map<Class<? extends BuildOutcome>, BuildOutcomeComparator<?, ?>> comparators = new HashMap<Class<? extends BuildOutcome>, BuildOutcomeComparator<?, ?>>();
+
+    public <T extends BuildOutcome> BuildOutcomeComparator<T, ?> getComparator(Class<T> outcomeType) {
+        BuildOutcomeComparator<?, ?> comparator = comparators.get(outcomeType);
+        if (comparator != null) {
+            //noinspection unchecked
+            return (BuildOutcomeComparator<T, ?>) comparator;
+        } else {
+            return null;
+        }
+    }
+
+    public void registerComparator(BuildOutcomeComparator<?, ?> comparator) {
+        comparators.put(comparator.getComparedType(), comparator);
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuilds.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuilds.java
new file mode 100644
index 0000000..c3f8da8
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuilds.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.api.internal.filestore.PathNormalisingKeyFileStore;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildComparisonResult;
+import org.gradle.api.plugins.buildcomparison.gradle.internal.ComparableGradleBuildExecuter;
+import org.gradle.api.plugins.buildcomparison.gradle.internal.DefaultGradleBuildInvocationSpec;
+import org.gradle.api.plugins.buildcomparison.gradle.internal.GradleBuildComparison;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcomeComparator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcomeHtmlRenderer;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcomeComparator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcomeComparisonResultHtmlRenderer;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcomeHtmlRenderer;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.VerificationTask;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.logging.ConsoleRenderer;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.GradleVersion;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Executes two Gradle builds (that can be the same build) with specified versions and compares the outcomes.
+ *
+ * Please see the “Comparing Builds” chapter of the Gradle User Guide for more information.
+ */
+ at Incubating
+public class CompareGradleBuilds extends DefaultTask implements VerificationTask {
+
+    public static final List<String> DEFAULT_TASKS = Arrays.asList("clean", "assemble");
+
+    private static final String TMP_FILESTORAGE_PREFIX = "tmp-filestorage";
+
+    private final GradleBuildInvocationSpec sourceBuild;
+    private final GradleBuildInvocationSpec targetBuild;
+    private boolean ignoreFailures;
+    private Object reportDir;
+
+    private final FileResolver fileResolver;
+    private final ProgressLoggerFactory progressLoggerFactory;
+
+    @Inject
+    public CompareGradleBuilds(FileResolver fileResolver, ProgressLoggerFactory progressLoggerFactory, Instantiator instantiator) {
+        this.fileResolver = fileResolver;
+        this.progressLoggerFactory = progressLoggerFactory;
+
+        sourceBuild = instantiator.newInstance(DefaultGradleBuildInvocationSpec.class, fileResolver, getProject().getRootDir());
+        sourceBuild.setTasks(DEFAULT_TASKS);
+        targetBuild = instantiator.newInstance(DefaultGradleBuildInvocationSpec.class, fileResolver, getProject().getRootDir());
+        targetBuild.setTasks(DEFAULT_TASKS);
+
+        // Never up to date
+        getOutputs().upToDateWhen(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return false;
+            }
+        });
+    }
+
+    /**
+     * The specification of how to invoke the source build.
+     *
+     * Defaults to {@link org.gradle.api.Project#getRootDir() project.rootDir} with the current Gradle version
+     * and the tasks “clean assemble”.
+     *
+     * The {@code projectDir} must be the project directory of the root project if this is a multi project build.
+     *
+     * @return The specification of how to invoke the source build.
+     */
+    public GradleBuildInvocationSpec getSourceBuild() {
+        return sourceBuild;
+    }
+
+    /**
+     * Configures the source build.
+     *
+     * A Groovy closure can be used as the action.
+     * <pre>
+     * sourceBuild {
+     *   gradleVersion = "1.1"
+     * }
+     * </pre>
+     *
+     * @param config The configuration action.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void sourceBuild(Action<GradleBuildInvocationSpec> config) {
+        config.execute(getSourceBuild());
+    }
+
+    /**
+     * The specification of how to invoke the target build.
+     *
+     * Defaults to {@link org.gradle.api.Project#getRootDir() project.rootDir} with the current Gradle version
+     * and the tasks “clean assemble”.
+     *
+     * The {@code projectDir} must be the project directory of the root project if this is a multi project build.
+     *
+     * @return The specification of how to invoke the target build.
+     */
+    public GradleBuildInvocationSpec getTargetBuild() {
+        return targetBuild;
+    }
+
+    /**
+     * Configures the target build.
+     *
+     * A Groovy closure can be used as the action.
+     * <pre>
+     * targetBuild {
+     *   gradleVersion = "1.1"
+     * }
+     * </pre>
+     *
+     * @param config The configuration action.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void targetBuild(Action<GradleBuildInvocationSpec> config) {
+        config.execute(getTargetBuild());
+    }
+
+    /**
+     * Whether a comparison between non identical builds will fail the task execution.
+     *
+     * @return True if a comparison between non identical builds will fail the task execution, otherwise false.
+     */
+    public boolean getIgnoreFailures() {
+        return ignoreFailures;
+    }
+
+    /**
+     * Sets whether a comparison between non identical builds will fail the task execution.
+     *
+     * @param ignoreFailures false to fail the task on non identical builds, true to not fail the task. The default is false.
+     */
+    public void setIgnoreFailures(boolean ignoreFailures) {
+        this.ignoreFailures = ignoreFailures;
+    }
+
+    /**
+     * The directory that will contain the HTML comparison report and any other report files.
+     *
+     * @return The directory that will contain the HTML comparison report and any other report files.
+     */
+    @OutputDirectory
+    public File getReportDir() {
+        return reportDir == null ? null : fileResolver.resolve(reportDir);
+    }
+
+    /**
+     * Sets the directory that will contain the HTML comparison report and any other report files.
+     *
+     * The value will be evaluated by {@link Project#file(Object) project.file()}.
+     *
+     * @param reportDir The directory that will contain the HTML comparison report and any other report files.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public void setReportDir(Object reportDir) {
+        if (reportDir == null) {
+            throw new IllegalArgumentException("reportDir cannot be null");
+        }
+        this.reportDir = reportDir;
+    }
+
+    private File getReportFile() {
+        return new File(getReportDir(), GradleBuildComparison.HTML_REPORT_FILE_NAME);
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    @TaskAction
+    void compare() {
+        GradleBuildInvocationSpec sourceBuild = getSourceBuild();
+        GradleBuildInvocationSpec targetBuild = getTargetBuild();
+
+        if (sourceBuild.equals(targetBuild)) {
+            getLogger().warn("The source build and target build are identical. Set '{}.targetBuild.gradleVersion' if you want to compare with a different Gradle version.", getName());
+        }
+
+        ComparableGradleBuildExecuter sourceBuildExecuter = new ComparableGradleBuildExecuter(sourceBuild);
+        ComparableGradleBuildExecuter targetBuildExecuter = new ComparableGradleBuildExecuter(targetBuild);
+
+        Logger logger = getLogger();
+        ProgressLogger progressLogger = progressLoggerFactory.newOperation(getClass());
+        progressLogger.setDescription("Gradle Build Comparison");
+        progressLogger.setShortDescription(getName());
+
+        GradleBuildComparison comparison = new GradleBuildComparison(
+                sourceBuildExecuter, targetBuildExecuter,
+                logger, progressLogger,
+                getProject().getGradle()
+        );
+
+        comparison.registerType(
+                GeneratedArchiveBuildOutcome.class,
+                new GeneratedArchiveBuildOutcomeComparator(),
+                new GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer(),
+                new GeneratedArchiveBuildOutcomeHtmlRenderer()
+        );
+
+        comparison.registerType(
+                UnknownBuildOutcome.class,
+                new UnknownBuildOutcomeComparator(),
+                new UnknownBuildOutcomeComparisonResultHtmlRenderer(),
+                new UnknownBuildOutcomeHtmlRenderer()
+        );
+
+        File fileStoreTmpBase = fileResolver.resolve(String.format(TMP_FILESTORAGE_PREFIX + "-%s-%s", getName(), System.currentTimeMillis()));
+        FileStore<String> fileStore = new PathNormalisingKeyFileStore(fileStoreTmpBase);
+
+        Map<String, String> hostAttributes = new LinkedHashMap<String, String>(4);
+        hostAttributes.put("Project", getProject().getRootDir().getAbsolutePath());
+        hostAttributes.put("Task", getPath());
+        hostAttributes.put("Gradle version", GradleVersion.current().getVersion());
+        hostAttributes.put("Executed at", new SimpleDateFormat().format(new Date()));
+
+        BuildComparisonResult result = comparison.compare(fileStore, getReportDir(), hostAttributes);
+        communicateResult(result);
+    }
+
+    private void communicateResult(BuildComparisonResult result) {
+        String reportUrl = new ConsoleRenderer().asClickableFileUrl(getReportFile());
+        if (result.isBuildsAreIdentical()) {
+            getLogger().info("The build outcomes were found to be identical. See the report at: {}", reportUrl);
+        } else {
+            String message = String.format("The build outcomes were not found to be identical. See the report at: %s", reportUrl);
+            if (getIgnoreFailures()) {
+                getLogger().warn(message);
+            } else {
+                throw new GradleException(message);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuildsPlugin.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuildsPlugin.groovy
new file mode 100644
index 0000000..104621b
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/CompareGradleBuildsPlugin.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.api.plugins.buildcomparison.gradle
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.reporting.ReportingExtension
+
+/**
+ * Preconfigures the project to run a gradle build comparison.
+ */
+ at Incubating
+class CompareGradleBuildsPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.apply(plugin: ReportingBasePlugin)
+        ReportingExtension reportingExtension = project.extensions.findByType(ReportingExtension)
+
+        project.tasks.withType(CompareGradleBuilds) { CompareGradleBuilds task ->
+            task.conventionMapping.map("reportDir") { reportingExtension.file(task.name) }
+        }
+
+        project.task("compareGradleBuilds", type: CompareGradleBuilds) {}
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/GradleBuildInvocationSpec.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/GradleBuildInvocationSpec.java
new file mode 100644
index 0000000..9baa393
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/GradleBuildInvocationSpec.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.plugins.buildcomparison.gradle;
+
+import org.gradle.api.Incubating;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A specification for launching a Gradle build with a specified Gradle version.
+  */
+ at Incubating
+public interface GradleBuildInvocationSpec {
+
+    /**
+     * The “root” directory of the build.
+     *
+     * Defaults to the current build's root directory.
+     *
+     * @return The “root” project directory of the build. Never null.
+     */
+    File getProjectDir();
+
+    /**
+     * Sets the “root” directory of the build.
+     *
+     * This should not be the project directory of child project in a multi project build.
+     * It should always be the root of the multiproject build.
+     *
+     * The value is interpreted as a file as per {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param projectDir The “root” directory of the build.
+     */
+    void setProjectDir(Object projectDir);
+
+    /**
+     * The Gradle version to run the build with.
+     *
+     * Defaults to the current Gradle version of the running build.
+     *
+     * @return The Gradle version to run the build with. Never null.
+     */
+    String getGradleVersion();
+
+    /**
+     * Sets the Gradle version to run the build with.
+     *
+     * The value must be a valid, published, Gradle version number.
+     *
+     * Examples are:
+     * <ul>
+     * <li>{@code "1.1"}</li>
+     * <li>{@code "1.0-rc-1"}</li>
+     * </ul>
+     *
+     * @param gradleVersion The Gradle version to run the build with.
+     */
+    void setGradleVersion(String gradleVersion);
+
+    /**
+     * The tasks to execute.
+     *
+     * Defaults to an empty list.
+     *
+     * @return The tasks to execute.
+     */
+    List<String> getTasks();
+
+    /**
+     * Sets the tasks to execute.
+     *
+     * @param tasks The tasks to execute.
+     */
+    void setTasks(Iterable<String> tasks);
+
+    /**
+     * The command line arguments (excluding tasks) to invoke the build with.
+     *
+     * @return The command line arguments (excluding tasks) to invoke the build with.
+     */
+    List<String> getArguments();
+
+    /**
+     * Sets the command line arguments (excluding tasks) to invoke the build with.
+     * @param arguments The command line arguments (excluding tasks) to invoke the build with.
+     */
+    void setArguments(Iterable<String> arguments);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/ComparableGradleBuildExecuter.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/ComparableGradleBuildExecuter.java
new file mode 100644
index 0000000..b60d456
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/ComparableGradleBuildExecuter.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.plugins.buildcomparison.gradle.internal;
+
+import org.gradle.api.plugins.buildcomparison.gradle.GradleBuildInvocationSpec;
+import org.gradle.tooling.BuildLauncher;
+import org.gradle.tooling.ModelBuilder;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
+import org.gradle.util.GradleVersion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ComparableGradleBuildExecuter {
+
+    public static final GradleVersion PROJECT_OUTCOMES_MINIMUM_VERSION = GradleVersion.version("1.2");
+    public static final GradleVersion EXEC_MINIMUM_VERSION = GradleVersion.version("1.0");
+
+    private final GradleBuildInvocationSpec spec;
+
+    public ComparableGradleBuildExecuter(GradleBuildInvocationSpec spec) {
+        this.spec = spec;
+    }
+
+    public GradleBuildInvocationSpec getSpec() {
+        return spec;
+    }
+
+    public boolean isExecutable() {
+        return getGradleVersion().compareTo(EXEC_MINIMUM_VERSION) >= 0;
+    }
+
+    public GradleVersion getGradleVersion() {
+        return GradleVersion.version(getSpec().getGradleVersion());
+    }
+
+    public boolean isCanObtainProjectOutcomesModel() {
+        GradleVersion version = getGradleVersion();
+        boolean isMinimumVersionOrHigher = version.compareTo(PROJECT_OUTCOMES_MINIMUM_VERSION) >= 0;
+        //noinspection SimplifiableIfStatement
+        if (isMinimumVersionOrHigher) {
+            return true;
+        } else {
+            // Special handling for snapshots/RCs of the minimum version
+            return version.getVersionBase().equals(PROJECT_OUTCOMES_MINIMUM_VERSION.getVersionBase());
+        }
+    }
+
+    private List<String> getImpliedArguments() {
+        List<String> rawArgs = getSpec().getArguments();
+
+        // Note: we don't know for certain that this is how to invoke this functionality for this Gradle version.
+        //       unsure of any other alternative.
+        if (rawArgs.contains("-u") || rawArgs.contains("--no-search-upward")) {
+            return rawArgs;
+        } else {
+            List<String> ammendedArgs = new ArrayList<String>(rawArgs.size() + 1);
+            ammendedArgs.add("--no-search-upward");
+            ammendedArgs.addAll(rawArgs);
+            return ammendedArgs;
+        }
+    }
+
+    public ProjectOutcomes executeWith(ProjectConnection connection) {
+        List<String> tasksList = getSpec().getTasks();
+        String[] tasks = tasksList.toArray(new String[tasksList.size()]);
+        List<String> argumentsList = getImpliedArguments();
+        String[] arguments = argumentsList.toArray(new String[argumentsList.size()]);
+
+        if (isCanObtainProjectOutcomesModel()) {
+            // Run the build and get the build outcomes model
+            ModelBuilder<ProjectOutcomes> modelBuilder = connection.model(ProjectOutcomes.class);
+            return modelBuilder.
+                    withArguments(arguments).
+                    forTasks(tasks).
+                    get();
+        } else {
+            BuildLauncher buildLauncher = connection.newBuild();
+            buildLauncher.
+                    withArguments(arguments).
+                    forTasks(tasks).
+                    run();
+
+            return null;
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpec.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpec.java
new file mode 100644
index 0000000..8face84
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpec.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle.internal;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.plugins.buildcomparison.gradle.GradleBuildInvocationSpec;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DefaultGradleBuildInvocationSpec implements GradleBuildInvocationSpec {
+
+    private FileResolver fileResolver;
+    private Object projectDir;
+    private String gradleVersion = GradleVersion.current().getVersion();
+    private List<String> tasks = new LinkedList<String>();
+    private List<String> arguments = new LinkedList<String>();
+
+    public DefaultGradleBuildInvocationSpec(FileResolver fileResolver, Object projectDir) {
+        this.fileResolver = fileResolver;
+        this.projectDir = projectDir;
+    }
+
+    public File getProjectDir() {
+        return fileResolver.resolve(projectDir);
+    }
+
+    public void setProjectDir(Object projectDir) {
+        if (projectDir == null) {
+            throw new IllegalArgumentException("projectDir cannot be null");
+        }
+        this.projectDir = projectDir;
+    }
+
+    public String getGradleVersion() {
+        return gradleVersion;
+    }
+
+    public void setGradleVersion(String gradleVersion) {
+        if (gradleVersion == null) {
+            throw new IllegalArgumentException("gradleVersion cannot be null");
+        }
+        GradleVersion version = GradleVersion.version(gradleVersion);
+        if (!version.isValid()) {
+            throw new IllegalArgumentException(String.format("%s is not a valid Gradle version string (examples: '1.0', 1.0-rc-1'", gradleVersion));
+        }
+        this.gradleVersion = version.getVersion();
+    }
+
+    public List<String> getTasks() {
+        return tasks;
+    }
+
+    public void setTasks(Iterable<String> tasks) {
+        this.tasks = tasks == null ? new LinkedList<String>() : Lists.newLinkedList(tasks);
+    }
+
+    public List<String> getArguments() {
+        return arguments;
+    }
+
+    public void setArguments(Iterable<String> arguments) {
+        this.arguments = arguments == null ? new LinkedList<String>() : Lists.newLinkedList(arguments);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultGradleBuildInvocationSpec that = (DefaultGradleBuildInvocationSpec) o;
+
+        if (!getArguments().equals(that.getArguments())) {
+            return false;
+        }
+        if (!getGradleVersion().equals(that.getGradleVersion())) {
+            return false;
+        }
+        if (!getProjectDir().equals(that.getProjectDir())) {
+            return false;
+        }
+        if (!getTasks().equals(that.getTasks())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getProjectDir().hashCode();
+        result = 31 * result + getGradleVersion().hashCode();
+        result = 31 * result + getTasks().hashCode();
+        result = 31 * result + getArguments().hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "{"
+                + "dir: '" + getProjectDir().getAbsolutePath() + "'"
+                + ", tasks: '" + CollectionUtils.join(" ", getTasks()) + "'"
+                + ", arguments: '" + CollectionUtils.join(" ", getArguments()) + "'"
+                + ", gradleVersion: " + getGradleVersion()
+                + "}";
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
new file mode 100644
index 0000000..af3188b
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle.internal;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.plugins.buildcomparison.compare.internal.*;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.ByTypeAndNameBuildOutcomeAssociator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.CompositeBuildOutcomeAssociator;
+import org.gradle.api.plugins.buildcomparison.render.internal.*;
+import org.gradle.api.plugins.buildcomparison.render.internal.html.GradleBuildComparisonResultHtmlRenderer;
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GradleVersion;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class GradleBuildComparison {
+
+    private static final String SOURCE_FILESTORE_PREFIX = "source";
+    private static final String TARGET_FILESTORE_PREFIX = "target";
+
+    public static final String HTML_REPORT_FILE_NAME = "index.html";
+    private static final String FILES_DIR_NAME = "files";
+
+    private final ComparableGradleBuildExecuter sourceBuildExecuter;
+    private final ComparableGradleBuildExecuter targetBuildExecuter;
+
+    private final DefaultBuildOutcomeComparatorFactory outcomeComparatorFactory = new DefaultBuildOutcomeComparatorFactory();
+    private final List<BuildOutcomeAssociator> outcomeAssociators = new LinkedList<BuildOutcomeAssociator>();
+    private final DefaultBuildOutcomeComparisonResultRendererFactory<HtmlRenderContext> comparisonResultRenderers = new DefaultBuildOutcomeComparisonResultRendererFactory<HtmlRenderContext>(HtmlRenderContext.class);
+    private final DefaultBuildOutcomeRendererFactory<HtmlRenderContext> outcomeRenderers = new DefaultBuildOutcomeRendererFactory<HtmlRenderContext>(HtmlRenderContext.class);
+    private final Logger logger;
+    private final ProgressLogger progressLogger;
+    private final Gradle gradle;
+
+    public GradleBuildComparison(
+            ComparableGradleBuildExecuter sourceBuildExecuter,
+            ComparableGradleBuildExecuter targetBuildExecuter,
+            Logger logger,
+            ProgressLogger progressLogger,
+            Gradle gradle) {
+        this.sourceBuildExecuter = sourceBuildExecuter;
+        this.targetBuildExecuter = targetBuildExecuter;
+        this.logger = logger;
+        this.progressLogger = progressLogger;
+        this.gradle = gradle;
+    }
+
+    public <T extends BuildOutcome, R extends BuildOutcomeComparisonResult<T>> void registerType(
+            Class<T> outcomeType,
+            BuildOutcomeComparator<T, R> outcomeComparator,
+            BuildOutcomeComparisonResultRenderer<R, HtmlRenderContext> comparisonResultRenderer,
+            BuildOutcomeRenderer<T, HtmlRenderContext> outcomeRenderer
+    ) {
+        outcomeComparatorFactory.registerComparator(outcomeComparator);
+        comparisonResultRenderers.registerRenderer(comparisonResultRenderer);
+        outcomeRenderers.registerRenderer(outcomeRenderer);
+        outcomeAssociators.add(new ByTypeAndNameBuildOutcomeAssociator<T>(outcomeType));
+    }
+
+    private String executingMessage(String name, ComparableGradleBuildExecuter executer) {
+        return String.format("executing %s build %s", name, executer.getSpec());
+    }
+
+    public BuildComparisonResult compare(FileStore<String> fileStore, File reportDir, Map<String, String> hostAttributes) {
+        String executingSourceBuildMessage = executingMessage("source", sourceBuildExecuter);
+        String executingTargetBuildMessage = executingMessage("target", targetBuildExecuter);
+
+        if (!sourceBuildExecuter.isExecutable() || !targetBuildExecuter.isExecutable()) {
+            throw new GradleException(String.format(
+                    "Builds must be executed with %s or newer (source: %s, target: %s)",
+                    ComparableGradleBuildExecuter.EXEC_MINIMUM_VERSION,
+                    sourceBuildExecuter.getSpec().getGradleVersion(),
+                    targetBuildExecuter.getSpec().getGradleVersion()
+            ));
+        }
+
+        boolean sourceBuildHasOutcomesModel = sourceBuildExecuter.isCanObtainProjectOutcomesModel();
+        boolean targetBuildHasOutcomesModel = targetBuildExecuter.isCanObtainProjectOutcomesModel();
+
+        if (!sourceBuildHasOutcomesModel && !targetBuildHasOutcomesModel) {
+            throw new GradleException(String.format(
+                    "Cannot run comparison because both the source and target build are to be executed with a Gradle version older than %s (source: %s, target: %s).",
+                    ComparableGradleBuildExecuter.PROJECT_OUTCOMES_MINIMUM_VERSION,
+                    sourceBuildExecuter.getSpec().getGradleVersion(),
+                    targetBuildExecuter.getSpec().getGradleVersion()
+            ));
+        }
+
+        if (!sourceBuildHasOutcomesModel) {
+            warnAboutInferredOutcomes(true, sourceBuildExecuter);
+        }
+        if (!targetBuildHasOutcomesModel) {
+            warnAboutInferredOutcomes(false, targetBuildExecuter);
+        }
+
+        Set<BuildOutcome> sourceOutcomes = null;
+        if (sourceBuildHasOutcomesModel) {
+            logger.info(executingSourceBuildMessage);
+            progressLogger.started(executingSourceBuildMessage);
+            ProjectOutcomes sourceOutput = executeBuild(sourceBuildExecuter);
+            progressLogger.progress("inspecting source build outcomes");
+            GradleBuildOutcomeSetTransformer sourceOutcomeTransformer = createOutcomeSetTransformer(fileStore, SOURCE_FILESTORE_PREFIX);
+            sourceOutcomes = sourceOutcomeTransformer.transform(sourceOutput);
+        }
+
+        logger.info(executingTargetBuildMessage);
+        if (sourceBuildHasOutcomesModel) {
+            progressLogger.progress(executingTargetBuildMessage);
+        } else {
+            progressLogger.started(executingTargetBuildMessage);
+        }
+
+        ProjectOutcomes targetOutput = executeBuild(targetBuildExecuter);
+
+        Set<BuildOutcome> targetOutcomes;
+        if (targetBuildHasOutcomesModel) {
+            progressLogger.progress("inspecting target build outcomes");
+            GradleBuildOutcomeSetTransformer targetOutcomeTransformer = createOutcomeSetTransformer(fileStore, TARGET_FILESTORE_PREFIX);
+            targetOutcomes = targetOutcomeTransformer.transform(targetOutput);
+        } else {
+            targetOutcomes = createOutcomeSetInferrer(fileStore, TARGET_FILESTORE_PREFIX, targetBuildExecuter.getSpec().getProjectDir()).transform(sourceOutcomes);
+        }
+
+        if (!sourceBuildHasOutcomesModel) {
+            logger.info(executingSourceBuildMessage);
+            progressLogger.progress(executingSourceBuildMessage);
+            executeBuild(sourceBuildExecuter);
+            progressLogger.progress("inspecting source build outcomes");
+            sourceOutcomes = createOutcomeSetInferrer(fileStore, SOURCE_FILESTORE_PREFIX, sourceBuildExecuter.getSpec().getProjectDir()).transform(targetOutcomes);
+        }
+
+        progressLogger.progress("comparing build outcomes");
+        BuildComparisonResult result = compareBuilds(sourceOutcomes, targetOutcomes);
+        writeReport(result, reportDir, fileStore, hostAttributes);
+        progressLogger.completed();
+
+        return result;
+    }
+
+    private void warnAboutInferredOutcomes(boolean isSource, ComparableGradleBuildExecuter executer) {
+        String inferred = isSource ? "source" : "target";
+        String inferredFrom = isSource ? "target" : "source";
+
+        String message = String.format(
+                "The build outcomes for the %s build will be inferred from the %s build because the %s build is to be executed with Gradle %s."
+                + " This means that the comparison accuracy will be reduced."
+                + " See the Gradle User Guide for more information.",
+                inferred, inferredFrom, inferred, executer.getSpec().getGradleVersion()
+        );
+
+        logger.warn(message);
+    }
+
+    private BuildComparisonResult compareBuilds(Set<BuildOutcome> sourceOutcomes, Set<BuildOutcome> targetOutcomes) {
+        BuildComparisonSpecFactory specFactory = new BuildComparisonSpecFactory(createBuildOutcomeAssociator());
+        BuildComparisonSpec comparisonSpec = specFactory.createSpec(sourceOutcomes, targetOutcomes);
+        BuildComparator buildComparator = new DefaultBuildComparator(outcomeComparatorFactory);
+        return buildComparator.compareBuilds(comparisonSpec);
+    }
+
+    private CompositeBuildOutcomeAssociator createBuildOutcomeAssociator() {
+        return new CompositeBuildOutcomeAssociator(outcomeAssociators);
+    }
+
+    private GradleBuildOutcomeSetTransformer createOutcomeSetTransformer(FileStore<String> fileStore, String filesPath) {
+        return new GradleBuildOutcomeSetTransformer(fileStore, filesPath);
+    }
+
+    private GradleBuildOutcomeSetInferrer createOutcomeSetInferrer(FileStore<String> fileStore, String filesPath, File baseDir) {
+        return new GradleBuildOutcomeSetInferrer(fileStore, filesPath, baseDir);
+    }
+
+    private ProjectOutcomes executeBuild(ComparableGradleBuildExecuter executer) {
+        ProjectConnection connection = createProjectConnection(executer);
+        try {
+            return executer.executeWith(connection);
+        } finally {
+            connection.close();
+        }
+    }
+
+    private ProjectConnection createProjectConnection(ComparableGradleBuildExecuter executer) {
+        GradleConnector connector = GradleConnector.newConnector();
+        connector.forProjectDirectory(executer.getSpec().getProjectDir());
+        File gradleUserHomeDir = gradle.getStartParameter().getGradleUserHomeDir();
+        if (gradleUserHomeDir != null) {
+            connector.useGradleUserHomeDir(gradleUserHomeDir);
+        }
+
+        GradleVersion gradleVersion = executer.getGradleVersion();
+        if (gradleVersion.equals(GradleVersion.current())) {
+            connector.useInstallation(gradle.getGradleHomeDir());
+        } else {
+            connector.useGradleVersion(gradleVersion.getVersion());
+        }
+
+        return connector.connect();
+    }
+
+    private void writeReport(BuildComparisonResult result, File reportDir, FileStore<String> fileStore, Map<String, String> hostAttributes) {
+        if (reportDir.exists() && reportDir.list().length > 0) {
+            GFileUtils.cleanDirectory(reportDir);
+        }
+
+        fileStore.moveFilestore(new File(reportDir, FILES_DIR_NAME));
+
+        Charset encoding = Charset.defaultCharset();
+        OutputStream outputStream;
+        Writer writer;
+
+        try {
+            outputStream = FileUtils.openOutputStream(new File(reportDir, HTML_REPORT_FILE_NAME));
+            writer = new OutputStreamWriter(outputStream, encoding);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        try {
+            createResultRenderer(encoding, reportDir, hostAttributes).render(result, writer);
+        } finally {
+            IOUtils.closeQuietly(writer);
+            IOUtils.closeQuietly(outputStream);
+        }
+    }
+
+    private BuildComparisonResultRenderer<Writer> createResultRenderer(Charset encoding, final File reportDir, final Map<String, String> hostAttributes) {
+        return new GradleBuildComparisonResultHtmlRenderer(
+                comparisonResultRenderers,
+                outcomeRenderers,
+                encoding,
+                sourceBuildExecuter,
+                targetBuildExecuter,
+                hostAttributes,
+                new Transformer<String, File>() {
+                    public String transform(File original) {
+                        return GFileUtils.relativePath(reportDir, original);
+                    }
+                }
+        );
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrer.java
new file mode 100644
index 0000000..3b15fd3
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrer.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.api.plugins.buildcomparison.gradle.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class GradleBuildOutcomeSetInferrer implements Transformer<Set<BuildOutcome>, Set<BuildOutcome>> {
+
+    private final FileStore<String> fileStore;
+    private final String fileStorePrefix;
+    private final File baseDir;
+
+    public GradleBuildOutcomeSetInferrer(FileStore<String> fileStore, String fileStorePrefix, File baseDir) {
+        this.fileStore = fileStore;
+        this.fileStorePrefix = fileStorePrefix;
+        this.baseDir = baseDir;
+    }
+
+    public Set<BuildOutcome> transform(Set<BuildOutcome> sourceOutcomes) {
+        return CollectionUtils.collect(sourceOutcomes, new HashSet<BuildOutcome>(sourceOutcomes.size()), new Transformer<BuildOutcome, BuildOutcome>() {
+            public BuildOutcome transform(BuildOutcome original) {
+                return infer(original);
+            }
+        });
+    }
+
+    private BuildOutcome infer(BuildOutcome outcome) {
+        if (outcome instanceof UnknownBuildOutcome) {
+            return new UnknownBuildOutcome(outcome.getName(), outcome.getDescription());
+        } else if (outcome instanceof GeneratedArchiveBuildOutcome) {
+            GeneratedArchiveBuildOutcome archiveBuildOutcome = (GeneratedArchiveBuildOutcome) outcome;
+            File file = new File(baseDir, archiveBuildOutcome.getRootRelativePath());
+            String rootRelativePath = archiveBuildOutcome.getRootRelativePath();
+
+            // TODO - we are relying on knowledge that the name of the outcome is the task path
+            String taskPath = outcome.getName();
+
+            FileStoreEntry fileStoreEntry = null;
+            if (file.exists()) {
+                String filestoreDestination = String.format("%s/%s/%s", fileStorePrefix, taskPath, file.getName());
+                fileStoreEntry = fileStore.move(filestoreDestination, file);
+            }
+
+            return new GeneratedArchiveBuildOutcome(outcome.getName(), outcome.getDescription(), fileStoreEntry, rootRelativePath);
+        } else {
+            throw new IllegalStateException(String.format("Unhandled build outcome type: %s", outcome.getClass().getName()));
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformer.java
new file mode 100644
index 0000000..d4b3933
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome;
+import org.gradle.tooling.internal.provider.FileOutcomeIdentifier;
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Transforms from the Gradle specific build outcomes into source agnostic outcomes.
+ */
+public class GradleBuildOutcomeSetTransformer implements Transformer<Set<BuildOutcome>, ProjectOutcomes> {
+
+    private final FileStore<String> fileStore;
+    private final String fileStorePrefix;
+
+    private final List<String> zipArchiveTypes = Arrays.asList(
+            FileOutcomeIdentifier.JAR_ARTIFACT.getTypeIdentifier(),
+            FileOutcomeIdentifier.EAR_ARTIFACT.getTypeIdentifier(),
+            FileOutcomeIdentifier.WAR_ARTIFACT.getTypeIdentifier(),
+            FileOutcomeIdentifier.ZIP_ARTIFACT.getTypeIdentifier()
+    );
+
+    public GradleBuildOutcomeSetTransformer(FileStore<String> fileStore, String fileStorePrefix) {
+        this.fileStore = fileStore;
+        this.fileStorePrefix = fileStorePrefix;
+    }
+
+    public Set<BuildOutcome> transform(ProjectOutcomes rootProject) {
+        Set<BuildOutcome> keyedOutcomes = new HashSet<BuildOutcome>();
+        addBuildOutcomes(rootProject, rootProject, keyedOutcomes);
+        return keyedOutcomes;
+    }
+
+    private void addBuildOutcomes(ProjectOutcomes projectOutcomes, ProjectOutcomes rootProject, Set<BuildOutcome> buildOutcomes) {
+        for (GradleBuildOutcome outcome : projectOutcomes.getOutcomes()) {
+            if (outcome instanceof GradleFileBuildOutcome) {
+                addFileBuildOutcome((GradleFileBuildOutcome) outcome, rootProject, buildOutcomes);
+            } else {
+                new UnknownBuildOutcome(outcome.getTaskPath(), outcome.getDescription());
+            }
+        }
+
+        for (ProjectOutcomes childProject : projectOutcomes.getChildren()) {
+            addBuildOutcomes(childProject, rootProject, buildOutcomes);
+        }
+    }
+
+    private void addFileBuildOutcome(GradleFileBuildOutcome outcome, ProjectOutcomes rootProject, Set<BuildOutcome> translatedOutcomes) {
+        if (zipArchiveTypes.contains(outcome.getTypeIdentifier())) {
+            File originalFile = outcome.getFile();
+            String relativePath = GFileUtils.relativePath(rootProject.getProjectDirectory(), originalFile);
+
+            FileStoreEntry fileStoreEntry = null;
+            if (originalFile.exists()) {
+                String filestoreDestination = String.format("%s/%s/%s", fileStorePrefix, outcome.getTaskPath(), originalFile.getName());
+                fileStoreEntry = fileStore.move(filestoreDestination, originalFile);
+            }
+
+            BuildOutcome buildOutcome = new GeneratedArchiveBuildOutcome(outcome.getTaskPath(), outcome.getDescription(), fileStoreEntry, relativePath);
+            translatedOutcomes.add(buildOutcome);
+        } else {
+            translatedOutcomes.add(new UnknownBuildOutcome(outcome.getTaskPath(), outcome.getDescription()));
+        }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/package-info.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/package-info.java
new file mode 100644
index 0000000..0564f73
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/package-info.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.
+ */
+
+/**
+ * Build comparision classes that are specific to Gradle, including comparing Gradle upgrades.
+ */
+package org.gradle.api.plugins.buildcomparison.gradle;
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcome.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcome.java
new file mode 100644
index 0000000..70295c2
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcome.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.plugins.buildcomparison.outcome.internal;
+
+import org.gradle.api.Named;
+
+/**
+ * Something that happens as a result of a build.
+ */
+public interface BuildOutcome extends Named {
+
+    /**
+     * A free form description of this outcome.
+     *
+     * @return A free form description of this outcome. Never null.
+     */
+    String getDescription();
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociation.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociation.java
new file mode 100644
index 0000000..794a302
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociation.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.buildcomparison.outcome.internal;
+
+/**
+ * Associates to related build outcomes that can be compared.
+ *
+ * @param <T> The common type of the build outcomes associated
+ */
+public interface BuildOutcomeAssociation<T extends BuildOutcome> {
+
+    /**
+     * The outcome on the from side.
+     *
+     * @return The outcome on the from side. Never null.
+     */
+    T getSource();
+
+    /**
+     * The outcome on the to side.
+     *
+     * @return The outcome on the from side. Never null.
+     */
+    T getTarget();
+
+    /**
+     * The common type of the from and to outcomes.
+     *
+     * The from and to outcomes may be subtypes or implementors of this type. This type
+     * will be used to determine how they should be compared.
+     *
+     * @return The common type of the from and to outcomes. Never null.
+     */
+    Class<T> getType();
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociator.java
new file mode 100644
index 0000000..2c36fe7
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeAssociator.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.plugins.buildcomparison.outcome.internal;
+
+public interface BuildOutcomeAssociator {
+
+    /**
+     * Attempts to associate the from outcome with the to outcome, by a type.
+     *
+     * @param source The outcome on the from side.
+     * @param target The outcome on the to side.
+     * @return The type of the association if they are associated, otherwise null.
+     */
+    Class<? extends BuildOutcome> findAssociationType(BuildOutcome source, BuildOutcome target);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeSupport.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeSupport.java
new file mode 100644
index 0000000..088420e
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/BuildOutcomeSupport.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.plugins.buildcomparison.outcome.internal;
+
+public abstract class BuildOutcomeSupport implements BuildOutcome {
+
+    private final String name;
+    private final String description;
+
+    protected BuildOutcomeSupport(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndCharacteristicBuildOutcomeAssociator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndCharacteristicBuildOutcomeAssociator.java
new file mode 100644
index 0000000..f677731
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndCharacteristicBuildOutcomeAssociator.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.plugins.buildcomparison.outcome.internal;
+
+import org.gradle.api.Transformer;
+
+public class ByTypeAndCharacteristicBuildOutcomeAssociator<T extends BuildOutcome> implements BuildOutcomeAssociator {
+
+    private final Class<? extends T> type;
+    private final Transformer<?, T> characteristicTransformer;
+
+    public ByTypeAndCharacteristicBuildOutcomeAssociator(Class<? extends T> type, Transformer<?, T> characteristicTransformer) {
+        this.type = type;
+        this.characteristicTransformer = characteristicTransformer;
+    }
+
+    public Class<? extends BuildOutcome> findAssociationType(BuildOutcome source, BuildOutcome target) {
+        if (type.isInstance(source) && type.isInstance(target)) {
+            Object fromCharacteristic = characteristicTransformer.transform(type.cast(source));
+            Object toCharacteristic = characteristicTransformer.transform(type.cast(target));
+
+            if (fromCharacteristic == null && toCharacteristic == null) {
+                return type;
+            } else if (fromCharacteristic == null || toCharacteristic == null) {
+                return null;
+            } else if (fromCharacteristic.equals(toCharacteristic)) {
+                return type;
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociator.java
new file mode 100644
index 0000000..67e0b3c
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociator.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.plugins.buildcomparison.outcome.internal;
+
+import org.gradle.api.Transformer;
+
+public class ByTypeAndNameBuildOutcomeAssociator<T extends BuildOutcome> extends ByTypeAndCharacteristicBuildOutcomeAssociator<T> {
+    public ByTypeAndNameBuildOutcomeAssociator(Class<? extends T> type) {
+        super(type, new Transformer<String, T>() {
+            public String transform(T outcome) {
+                return outcome.getName();
+            }
+        });
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/CompositeBuildOutcomeAssociator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/CompositeBuildOutcomeAssociator.java
new file mode 100644
index 0000000..27ff8c7
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/CompositeBuildOutcomeAssociator.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.api.plugins.buildcomparison.outcome.internal;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+public class CompositeBuildOutcomeAssociator implements BuildOutcomeAssociator {
+
+    private final List<BuildOutcomeAssociator> associators;
+
+    public CompositeBuildOutcomeAssociator(Iterable<BuildOutcomeAssociator> associators) {
+        this.associators = Lists.newLinkedList(associators);
+    }
+
+    public Class<? extends BuildOutcome> findAssociationType(BuildOutcome source, BuildOutcome target) {
+        for (BuildOutcomeAssociator associator : associators) {
+            Class<? extends BuildOutcome> outcomeType = associator.findAssociationType(source, target);
+            if (outcomeType != null) {
+                return outcomeType;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/DefaultBuildOutcomeAssociation.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/DefaultBuildOutcomeAssociation.java
new file mode 100644
index 0000000..d29e145
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/DefaultBuildOutcomeAssociation.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.api.plugins.buildcomparison.outcome.internal;
+
+public class DefaultBuildOutcomeAssociation<A extends BuildOutcome> implements BuildOutcomeAssociation<A> {
+
+    private final A source;
+    private final A target;
+    private final Class<A> type;
+
+    public <S extends A, T extends A> DefaultBuildOutcomeAssociation(S source, T target, Class<A> type) {
+        this.source = source;
+        this.target = target;
+        this.type = type;
+    }
+
+    public A getSource() {
+        return source;
+    }
+
+    public A getTarget() {
+        return target;
+    }
+
+    public Class<A> getType() {
+        return type;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcome.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcome.java
new file mode 100644
index 0000000..357f8cd
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcome.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.buildcomparison.outcome.internal.archive;
+
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeSupport;
+
+import java.io.File;
+
+public class GeneratedArchiveBuildOutcome extends BuildOutcomeSupport {
+
+    private final FileStoreEntry fileStoreEntry;
+    private final String rootRelativePath;
+
+    public GeneratedArchiveBuildOutcome(String name, String description, FileStoreEntry fileStoreEntry, String rootRelativePath) {
+        super(name, description);
+        this.fileStoreEntry = fileStoreEntry;
+        this.rootRelativePath = rootRelativePath;
+    }
+
+    /**
+     * The generated archive, may be null.
+     *
+     * If null, the archives was expected to have been generated but was not.
+     *
+     * @return The generated archive, or null if no archive was generated.
+     */
+    public File getArchiveFile() {
+        return fileStoreEntry == null ? null : fileStoreEntry.getFile();
+    }
+
+    /**
+     * The relative path to where this archive was generated, relative to some meaningful root.
+     *
+     * @return The relative path.
+     */
+    public String getRootRelativePath() {
+        return rootRelativePath;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparator.java
new file mode 100644
index 0000000..38e9d9c
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparator.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.api.plugins.buildcomparison.outcome.internal.archive;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntry;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ZipEntryToArchiveEntryTransformer;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntryComparison;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.FileToArchiveEntrySetTransformer;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.*;
+
+public class GeneratedArchiveBuildOutcomeComparator implements BuildOutcomeComparator<GeneratedArchiveBuildOutcome, GeneratedArchiveBuildOutcomeComparisonResult> {
+
+    private final Transformer<Set<ArchiveEntry>, File> archiveToEntriesTransformer;
+
+    public GeneratedArchiveBuildOutcomeComparator() {
+        this(new FileToArchiveEntrySetTransformer(new ZipEntryToArchiveEntryTransformer()));
+    }
+
+    GeneratedArchiveBuildOutcomeComparator(Transformer<Set<ArchiveEntry>, File> archiveToEntriesTransformer) {
+        this.archiveToEntriesTransformer = archiveToEntriesTransformer;
+    }
+
+    public Class<GeneratedArchiveBuildOutcome> getComparedType() {
+        return GeneratedArchiveBuildOutcome.class;
+    }
+
+    public GeneratedArchiveBuildOutcomeComparisonResult compare(BuildOutcomeAssociation<GeneratedArchiveBuildOutcome> association) {
+        GeneratedArchiveBuildOutcome source = association.getSource();
+        GeneratedArchiveBuildOutcome target = association.getTarget();
+
+        Set<ArchiveEntry> sourceEntries;
+        if (source.getArchiveFile() != null && source.getArchiveFile().exists()) {
+            sourceEntries = archiveToEntriesTransformer.transform(source.getArchiveFile());
+        } else {
+            sourceEntries = Collections.emptySet();
+        }
+
+        Set<ArchiveEntry> targetEntries;
+        if (target.getArchiveFile() != null && target.getArchiveFile().exists()) {
+            targetEntries = archiveToEntriesTransformer.transform(target.getArchiveFile());
+        } else {
+            targetEntries = Collections.emptySet();
+        }
+
+        CollectionUtils.SetDiff<ArchiveEntry> diff = CollectionUtils.diffSetsBy(sourceEntries, targetEntries, new Transformer<String, ArchiveEntry>() {
+            public String transform(ArchiveEntry entry) {
+                return entry.getPath();
+            }
+        });
+
+        SortedSet<ArchiveEntryComparison> entryComparisons = new TreeSet<ArchiveEntryComparison>(new Comparator<ArchiveEntryComparison>() {
+            public int compare(ArchiveEntryComparison o1, ArchiveEntryComparison o2) {
+                return o1.getPath().compareTo(o2.getPath());
+            }
+        });
+
+        for (ArchiveEntry sourceOnly : diff.leftOnly) {
+            entryComparisons.add(new ArchiveEntryComparison(sourceOnly.getPath(), sourceOnly, null));
+        }
+
+        for (CollectionUtils.SetDiff.Pair<ArchiveEntry> pair : diff.common) {
+            entryComparisons.add(new ArchiveEntryComparison(pair.left.getPath(), pair.left, pair.right));
+        }
+
+        for (ArchiveEntry targetOnly : diff.rightOnly) {
+            entryComparisons.add(new ArchiveEntryComparison(targetOnly.getPath(), null, targetOnly));
+        }
+
+        return new GeneratedArchiveBuildOutcomeComparisonResult(association, entryComparisons);
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResult.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResult.java
new file mode 100644
index 0000000..2a8592b
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.outcome.internal.archive;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResultSupport;
+import org.gradle.api.plugins.buildcomparison.compare.internal.ComparisonResultType;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntryComparison;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+
+import java.util.SortedSet;
+
+public class GeneratedArchiveBuildOutcomeComparisonResult extends BuildOutcomeComparisonResultSupport<GeneratedArchiveBuildOutcome> {
+
+    private final SortedSet<ArchiveEntryComparison> entryComparisons;
+
+    public GeneratedArchiveBuildOutcomeComparisonResult(BuildOutcomeAssociation<GeneratedArchiveBuildOutcome> compared, SortedSet<ArchiveEntryComparison> entryComparisons) {
+        super(compared);
+        this.entryComparisons = entryComparisons;
+    }
+
+    public SortedSet<ArchiveEntryComparison> getEntryComparisons() {
+        return entryComparisons;
+    }
+
+    public ComparisonResultType getComparisonResultType() {
+        boolean sourceFileExists = getCompared().getSource().getArchiveFile() != null;
+        boolean targetFileExists = getCompared().getTarget().getArchiveFile() != null;
+
+        if (sourceFileExists && targetFileExists) {
+            if (CollectionUtils.every(getEntryComparisons(), new Spec<ArchiveEntryComparison>() {
+                public boolean isSatisfiedBy(ArchiveEntryComparison element) {
+                    return element.getComparisonResultType() == ComparisonResultType.EQUAL;
+                }
+            })) {
+                return ComparisonResultType.EQUAL;
+            } else {
+                return ComparisonResultType.UNEQUAL;
+            }
+        } else if (!sourceFileExists && !targetFileExists) {
+            return ComparisonResultType.NON_EXISTENT;
+        } else if (!targetFileExists) {
+            return ComparisonResultType.SOURCE_ONLY;
+        } else {
+            return ComparisonResultType.TARGET_ONLY;
+        }
+    }
+
+    public boolean isOutcomesAreIdentical() {
+        return getComparisonResultType().equals(ComparisonResultType.EQUAL);
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer.groovy
new file mode 100644
index 0000000..28de496
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer.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.api.plugins.buildcomparison.outcome.internal.archive
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntry
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntryComparison
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeComparisonResultHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+import static org.gradle.api.plugins.buildcomparison.compare.internal.ComparisonResultType.*
+
+/*
+    TODO - missing test coverage
+ */
+class GeneratedArchiveBuildOutcomeComparisonResultHtmlRenderer extends BuildOutcomeComparisonResultHtmlRenderer<GeneratedArchiveBuildOutcomeComparisonResult> {
+
+    Class<GeneratedArchiveBuildOutcomeComparisonResult> getResultType() {
+        return GeneratedArchiveBuildOutcomeComparisonResult.class;
+    }
+
+    void render(GeneratedArchiveBuildOutcomeComparisonResult result, HtmlRenderContext context) {
+        renderTitle(result, context)
+
+        def source = result.compared.source
+        def target = result.compared.target
+
+        context.render {
+            table {
+                tr {
+                    th class: "border-right", ""
+                    th "Original Location (relative to project root)"
+                    th "Archive Copy (relative to this report)"
+                }
+                tr {
+                    th class: "border-right no-border-bottom", "Source"
+                    td source.rootRelativePath
+                    if (source.archiveFile) {
+                        def sourceCopyPath = context.relativePath(source.archiveFile)
+                        td { a(href: sourceCopyPath, sourceCopyPath) }
+                    } else {
+                        td "(no file; not created by build)"
+                    }
+                }
+                tr {
+                    th class: "border-right no-border-bottom", "Target"
+                    td target.rootRelativePath
+                    if (target.archiveFile) {
+                        def targetCopyPath = context.relativePath(target.archiveFile)
+                        td { a(href: targetCopyPath, targetCopyPath) }
+                    } else {
+                        td "(no file; not created by build)"
+                    }
+                }
+            }
+        }
+
+        if (result.comparisonResultType == NON_EXISTENT) {
+            resultMsg "Neither side produced the archive.", false, context
+        } else if (result.comparisonResultType == SOURCE_ONLY) {
+            resultMsg "The archive was only produced by the source build.", false, context
+        } else if (result.comparisonResultType == TARGET_ONLY) {
+            resultMsg "The archive was only produced by the target build.", false, context
+        } else if (result.comparisonResultType == EQUAL) {
+            resultMsg "The archives are completely identical.", true, context
+        } else if (result.comparisonResultType == UNEQUAL) {
+            resultMsg "There are differences within the archive.", false, context
+            renderUnequal(context, result.entryComparisons)
+        } else {
+            result.comparisonResultType.throwUnsupported()
+        }
+    }
+
+    private resultMsg(String msg, boolean equal, HtmlRenderContext context) {
+        context.render { p(class: "${context.equalOrDiffClass(equal)} ${context.comparisonResultMsgClass}", msg) }
+    }
+
+    private void renderUnequal(HtmlRenderContext context, Iterable<ArchiveEntryComparison> entryComparisons) {
+        context.render {
+
+            h5 "Entry Differences"
+            table(class: "archive-entry-differences") {
+                tr {
+                    th "Path"
+                    th "Difference"
+                }
+
+                entryComparisons.each { entryComparison ->
+                    if (entryComparison.comparisonResultType != EQUAL) {
+                        tr {
+                            td entryComparison.path
+                            td toDifferenceDescription(entryComparison)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("GroovyMissingReturnStatement")
+    protected String toDifferenceDescription(ArchiveEntryComparison entryComparison) {
+        switch (entryComparison.comparisonResultType) {
+            case SOURCE_ONLY:
+                "entry does not exist in target build archive"
+                break
+            case TARGET_ONLY:
+                "entry does not exist in source build archive"
+                break
+            case UNEQUAL:
+                toDifferenceDescription(entryComparison.source, entryComparison.target)
+                break
+            default:
+                entryComparison.comparisonResultType.throwUnsupported()
+        }
+    }
+
+    protected String toDifferenceDescription(ArchiveEntry source, ArchiveEntry target) {
+        if (source.directory != target.directory) {
+            if (source.directory) {
+                "entry is a directory in the source build and a file in the target build"
+            } else {
+                "entry is a directory in the target build and a file in the source build"
+            }
+        } else if (source.size != target.size) {
+            "entry in the source build is $source.size bytes - in the target build it is $target.size bytes (${formatSizeDiff(target.size - source.size)})"
+        } else if (source.crc != target.crc) {
+            "entries are of identical size but have different content"
+        } else {
+            throw new IllegalStateException("Method was called with equal entries")
+        }
+    }
+
+    protected String formatSizeDiff(Long diff) {
+        (diff > 0 ? "+" : "") + diff.toString()
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeHtmlRenderer.groovy
new file mode 100644
index 0000000..8a16395
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeHtmlRenderer.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.api.plugins.buildcomparison.outcome.internal.archive
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+class GeneratedArchiveBuildOutcomeHtmlRenderer extends BuildOutcomeHtmlRenderer<GeneratedArchiveBuildOutcome> {
+
+    final Class<GeneratedArchiveBuildOutcome> outcomeType = GeneratedArchiveBuildOutcome
+
+    void render(GeneratedArchiveBuildOutcome outcome, HtmlRenderContext context) {
+        renderTitle(outcome, context)
+        context.render {
+            table {
+                tr {
+                    th "Original Location (relative to project root)"
+                    th "Archive Copy (relative to this report)"
+                }
+                tr {
+                    td outcome.rootRelativePath
+                    if (outcome.archiveFile) {
+                        def sourceCopyPath = context.relativePath(outcome.archiveFile)
+                        td { a(href: sourceCopyPath, sourceCopyPath) }
+                    } else {
+                        td "(no file; not created by build)"
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntry.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntry.java
new file mode 100644
index 0000000..917b406
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntry.java
@@ -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.api.plugins.buildcomparison.outcome.internal.archive.entry;
+
+public class ArchiveEntry {
+
+    private String path;
+    private boolean directory;
+    private long size = -1;
+    private long crc = -1;
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public boolean isDirectory() {
+        return directory;
+    }
+
+    public void setDirectory(boolean directory) {
+        this.directory = directory;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public long getCrc() {
+        return crc;
+    }
+
+    public void setCrc(long crc) {
+        this.crc = crc;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ArchiveEntry that = (ArchiveEntry) o;
+
+        if (crc != that.crc) {
+            return false;
+        }
+        if (directory != that.directory) {
+            return false;
+        }
+        if (size != that.size) {
+            return false;
+        }
+        //noinspection RedundantIfStatement
+        if (path != null ? !path.equals(that.path) : that.path != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = path != null ? path.hashCode() : 0;
+        result = 31 * result + (directory ? 1 : 0);
+        result = 31 * result + (int) (size ^ (size >>> 32));
+        result = 31 * result + (int) (crc ^ (crc >>> 32));
+        return result;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparison.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparison.java
new file mode 100644
index 0000000..dba6dbb
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparison.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.api.plugins.buildcomparison.outcome.internal.archive.entry;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.ComparisonResultType;
+
+public class ArchiveEntryComparison {
+
+    private final String path;
+    private final ArchiveEntry source;
+    private final ArchiveEntry target;
+
+    public ArchiveEntryComparison(String path, ArchiveEntry source, ArchiveEntry target) {
+        if (source == null && target == null) {
+            throw new IllegalArgumentException("Both 'from' and 'to' cannot be null");
+        }
+
+        this.path = path;
+        this.source = source;
+        this.target = target;
+    }
+
+    public ComparisonResultType getComparisonResultType() {
+        if (source != null && target == null) {
+            return ComparisonResultType.SOURCE_ONLY;
+        } else if (source == null && target != null) {
+            return ComparisonResultType.TARGET_ONLY;
+        } else {
+            //noinspection ConstantConditions
+            return source.equals(target) ? ComparisonResultType.EQUAL : ComparisonResultType.UNEQUAL;
+        }
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public ArchiveEntry getSource() {
+        return source;
+    }
+
+    public ArchiveEntry getTarget() {
+        return target;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformer.java
new file mode 100644
index 0000000..dd49b0e
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformer.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.plugins.buildcomparison.outcome.internal.archive.entry;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class FileToArchiveEntrySetTransformer implements Transformer<Set<ArchiveEntry>, File> {
+
+    private final Transformer<ArchiveEntry, ZipEntry> entryTransformer;
+
+    public FileToArchiveEntrySetTransformer(Transformer<ArchiveEntry, ZipEntry> entryTransformer) {
+        this.entryTransformer = entryTransformer;
+    }
+
+    public Set<ArchiveEntry> transform(File archiveFile) {
+        Set<ArchiveEntry> entries = new HashSet<ArchiveEntry>();
+
+        FileInputStream fileInputStream;
+        try {
+            fileInputStream = new FileInputStream(archiveFile);
+        } catch (FileNotFoundException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        ZipInputStream zipStream = new ZipInputStream(fileInputStream);
+
+        try {
+            ZipEntry entry = zipStream.getNextEntry();
+            while (entry != null) {
+                entries.add(entryTransformer.transform(entry));
+                zipStream.closeEntry();
+                entry = zipStream.getNextEntry();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(zipStream);
+        }
+
+        return entries;
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformer.java
new file mode 100644
index 0000000..0dae5be
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformer.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.plugins.buildcomparison.outcome.internal.archive.entry;
+
+import org.gradle.api.Transformer;
+
+import java.util.zip.ZipEntry;
+
+public class ZipEntryToArchiveEntryTransformer implements Transformer<ArchiveEntry, ZipEntry> {
+
+    public ArchiveEntry transform(ZipEntry zipEntry) {
+        ArchiveEntry archiveEntry = new ArchiveEntry();
+        archiveEntry.setPath(zipEntry.getName());
+        archiveEntry.setCrc(zipEntry.getCrc());
+        archiveEntry.setDirectory(zipEntry.isDirectory());
+        archiveEntry.setSize(zipEntry.getSize());
+        return archiveEntry;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcome.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcome.java
new file mode 100644
index 0000000..e53705c
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcome.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.plugins.buildcomparison.outcome.internal.unknown;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeSupport;
+
+/**
+ * A build outcome that is not known or understood by this version of Gradle.
+ */
+public class UnknownBuildOutcome extends BuildOutcomeSupport {
+
+    public UnknownBuildOutcome(String name, String description) {
+        super(name, description);
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparator.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparator.java
new file mode 100644
index 0000000..106acbc
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparator.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.plugins.buildcomparison.outcome.internal.unknown;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparator;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+public class UnknownBuildOutcomeComparator implements BuildOutcomeComparator<UnknownBuildOutcome, UnknownBuildOutcomeComparisonResult> {
+
+    public Class<UnknownBuildOutcome> getComparedType() {
+        return UnknownBuildOutcome.class;
+    }
+
+    public UnknownBuildOutcomeComparisonResult compare(BuildOutcomeAssociation<UnknownBuildOutcome> association) {
+        return new UnknownBuildOutcomeComparisonResult(association);
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResult.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResult.java
new file mode 100644
index 0000000..f8ea7e8
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResult.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.plugins.buildcomparison.outcome.internal.unknown;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResultSupport;
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation;
+
+public class UnknownBuildOutcomeComparisonResult extends BuildOutcomeComparisonResultSupport<UnknownBuildOutcome> {
+
+    public UnknownBuildOutcomeComparisonResult(BuildOutcomeAssociation<UnknownBuildOutcome> compared) {
+        super(compared);
+    }
+
+    public boolean isOutcomesAreIdentical() {
+        return false;
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResultHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResultHtmlRenderer.groovy
new file mode 100644
index 0000000..7bd46c9
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeComparisonResultHtmlRenderer.groovy
@@ -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.plugins.buildcomparison.outcome.internal.unknown
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeComparisonResultHtmlRenderer
+
+class UnknownBuildOutcomeComparisonResultHtmlRenderer extends BuildOutcomeComparisonResultHtmlRenderer<UnknownBuildOutcomeComparisonResult> {
+
+    Class<UnknownBuildOutcomeComparisonResult> getResultType() {
+        UnknownBuildOutcomeComparisonResult
+    }
+
+    void render(UnknownBuildOutcomeComparisonResult result, HtmlRenderContext context) {
+        renderTitle(result, context)
+
+        context.render {
+            p "This version of Gradle does not understand this kind of build outcome."
+            p "Running the comparison process from a newer version of Gradle may yield better results."
+        }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeHtmlRenderer.groovy
new file mode 100644
index 0000000..393a751
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/unknown/UnknownBuildOutcomeHtmlRenderer.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.api.plugins.buildcomparison.outcome.internal.unknown
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+class UnknownBuildOutcomeHtmlRenderer extends BuildOutcomeHtmlRenderer<UnknownBuildOutcome> {
+
+    final Class<UnknownBuildOutcome> outcomeType = UnknownBuildOutcome
+
+    void render(UnknownBuildOutcome outcome, HtmlRenderContext context) {
+        renderTitle(outcome, context)
+
+        context.render {
+            p "This version of Gradle does not understand this kind of build outcome."
+            p "Running the comparison process from a newer version of Gradle may yield better results."
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildComparisonResultRenderer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildComparisonResultRenderer.java
new file mode 100644
index 0000000..410f664
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildComparisonResultRenderer.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildComparisonResult;
+
+/**
+ * Renders a build comparison result to a context.
+ *
+ * @param <C> The type of the context object.
+ */
+public interface BuildComparisonResultRenderer<C> {
+
+    void render(BuildComparisonResult result, C context);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRenderer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRenderer.java
new file mode 100644
index 0000000..d0c36f2
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRenderer.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult;
+
+/**
+ * An object that can render a comparison result to some context.
+ *
+ * @param <T> The type of result that this renderer handles
+ * @param <C> The type of the context object for the render
+ */
+public interface BuildOutcomeComparisonResultRenderer<T extends BuildOutcomeComparisonResult, C> {
+
+    Class<T> getResultType();
+
+    Class<C> getContextType();
+
+    void render(T result, C context);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRendererFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRendererFactory.java
new file mode 100644
index 0000000..4bc2bf2
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeComparisonResultRendererFactory.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult;
+
+public interface BuildOutcomeComparisonResultRendererFactory<C> {
+
+    <T extends BuildOutcomeComparisonResult> BuildOutcomeComparisonResultRenderer<T, C> getRenderer(Class<? extends T> resultType);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRenderer.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRenderer.java
new file mode 100644
index 0000000..7e49e73
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRenderer.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+/**
+ * An object that can render a build outcome.
+ *
+ * @param <T> The type of outcome that this renderer handles
+ * @param <C> The type of the context object for the render
+ */
+public interface BuildOutcomeRenderer<T extends BuildOutcome, C> {
+
+    Class<T> getOutcomeType();
+
+    Class<C> getContextType();
+
+    void render(T outcome, C context);
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRendererFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRendererFactory.java
new file mode 100644
index 0000000..7b075c6
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/BuildOutcomeRendererFactory.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+public interface BuildOutcomeRendererFactory<C> {
+
+    <T extends BuildOutcome> BuildOutcomeRenderer<T, C> getRenderer(Class<? extends T> outcomeType);
+
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactory.java
new file mode 100644
index 0000000..e60c37f
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactory.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.plugins.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultBuildOutcomeComparisonResultRendererFactory<C> implements BuildOutcomeComparisonResultRendererFactory<C> {
+
+    private final Class<C> contextType;
+
+    private final Map<Class<? extends BuildOutcomeComparisonResult>, BuildOutcomeComparisonResultRenderer<?, C>> renderers =
+            new HashMap<Class<? extends BuildOutcomeComparisonResult>, BuildOutcomeComparisonResultRenderer<?, C>>();
+
+    public DefaultBuildOutcomeComparisonResultRendererFactory(Class<C> contextType) {
+        this.contextType = contextType;
+    }
+
+    public <T extends BuildOutcomeComparisonResult> void registerRenderer(BuildOutcomeComparisonResultRenderer<T, C> renderer) {
+        if (renderer.getContextType().isAssignableFrom(contextType)) {
+            renderers.put(renderer.getResultType(), renderer);
+        } else {
+            throw new IllegalArgumentException(
+                    String.format("Renderer '%s' has context type '%s' which is incompatible with target context type '%s'", renderer, renderer.getContextType(), contextType)
+            );
+        }
+    }
+
+    public <T extends BuildOutcomeComparisonResult> BuildOutcomeComparisonResultRenderer<T, C> getRenderer(Class<? extends T> resultType) {
+        //noinspection unchecked
+        return (BuildOutcomeComparisonResultRenderer<T, C>) renderers.get(resultType);
+    }
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeRendererFactory.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeRendererFactory.java
new file mode 100644
index 0000000..c93bd3e
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeRendererFactory.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.buildcomparison.render.internal;
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultBuildOutcomeRendererFactory<C> implements BuildOutcomeRendererFactory<C> {
+
+    private final Class<C> contextType;
+
+    private final Map<Class<? extends BuildOutcome>, BuildOutcomeRenderer<?, C>> renderers =
+            new HashMap<Class<? extends BuildOutcome>, BuildOutcomeRenderer<?, C>>();
+
+    public DefaultBuildOutcomeRendererFactory(Class<C> contextType) {
+        this.contextType = contextType;
+    }
+
+    public <T extends BuildOutcome> void registerRenderer(BuildOutcomeRenderer<T, C> renderer) {
+        if (renderer.getContextType().isAssignableFrom(contextType)) {
+            renderers.put(renderer.getOutcomeType(), renderer);
+        } else {
+            throw new IllegalArgumentException(
+                    String.format("Renderer '%s' has context type '%s' which is incompatible with target context type '%s'", renderer, renderer.getContextType(), contextType)
+            );
+        }
+    }
+
+    public <T extends BuildOutcome> BuildOutcomeRenderer<T, C> getRenderer(Class<? extends T> resultType) {
+        //noinspection unchecked
+        return (BuildOutcomeRenderer<T, C>) renderers.get(resultType);
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeComparisonResultHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeComparisonResultHtmlRenderer.groovy
new file mode 100644
index 0000000..270f423
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeComparisonResultHtmlRenderer.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.api.plugins.buildcomparison.render.internal.html
+
+import org.gradle.api.plugins.buildcomparison.render.internal.BuildOutcomeComparisonResultRenderer
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult
+
+abstract class BuildOutcomeComparisonResultHtmlRenderer<T extends BuildOutcomeComparisonResult> implements BuildOutcomeComparisonResultRenderer<T, HtmlRenderContext> {
+
+    Class<HtmlRenderContext> getContextType() {
+        HtmlRenderContext
+    }
+
+    protected void renderTitle(T result, HtmlRenderContext context) {
+        // TODO - assuming that both sides have the same name, which they always do in 1.2
+        context.render { h3 result.compared.source.name }
+    }
+
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeHtmlRenderer.groovy
new file mode 100644
index 0000000..caaa82e
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/BuildOutcomeHtmlRenderer.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.api.plugins.buildcomparison.render.internal.html
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+import org.gradle.api.plugins.buildcomparison.render.internal.BuildOutcomeRenderer
+
+abstract class BuildOutcomeHtmlRenderer<T extends BuildOutcome> implements BuildOutcomeRenderer<T, HtmlRenderContext> {
+
+    final Class<HtmlRenderContext> contextType = HtmlRenderContext
+
+    protected void renderTitle(T result, HtmlRenderContext context) {
+        context.render { h3 result.name }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRenderer.groovy b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRenderer.groovy
new file mode 100644
index 0000000..8c9f1b0
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRenderer.groovy
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.render.internal.html
+
+import groovy.xml.MarkupBuilder
+import org.apache.commons.lang.StringEscapeUtils
+import org.gradle.api.Transformer
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildComparisonResult
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult
+import org.gradle.api.plugins.buildcomparison.gradle.internal.ComparableGradleBuildExecuter
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+
+import java.nio.charset.Charset
+
+import org.gradle.api.plugins.buildcomparison.render.internal.*
+
+class GradleBuildComparisonResultHtmlRenderer implements BuildComparisonResultRenderer<Writer> {
+
+    private final BuildOutcomeComparisonResultRendererFactory<HtmlRenderContext> comparisonRenderers
+    private final BuildOutcomeRendererFactory outcomeRenderers
+
+    private final Transformer<String, File> filePathRelativizer
+    private final ComparableGradleBuildExecuter sourceExecuter
+    private final ComparableGradleBuildExecuter targetExecuter
+    private final Map<String, String> hostAttributes
+    private final Charset encoding
+
+    GradleBuildComparisonResultHtmlRenderer(
+            BuildOutcomeComparisonResultRendererFactory<HtmlRenderContext> comparisonRenderers,
+            BuildOutcomeRendererFactory outcomeRenderers,
+            Charset encoding,
+            ComparableGradleBuildExecuter sourceExecuter,
+            ComparableGradleBuildExecuter targetExecuter,
+            Map<String, String> hostAttributes,
+            Transformer<String, File> fileRelativizer
+    ) {
+        this.comparisonRenderers = comparisonRenderers
+        this.outcomeRenderers = outcomeRenderers
+        this.encoding = encoding
+        this.sourceExecuter = sourceExecuter
+        this.targetExecuter = targetExecuter
+        this.hostAttributes = hostAttributes
+        this.filePathRelativizer = fileRelativizer
+    }
+
+    void render(BuildComparisonResult result, Writer writer) {
+        MarkupBuilder markupBuilder = new MarkupBuilder(new IndentPrinter(writer))
+        HtmlRenderContext context = new HtmlRenderContext(markupBuilder, filePathRelativizer)
+
+        markupBuilder.html {
+            head {
+                renderHead(result, context)
+            }
+            body {
+                div("class": "text-container") {
+                    renderHeading(result, context)
+                    renderOutcomeComparisons(result, context)
+                    renderUncomparedOutcomes(result, context)
+                }
+            }
+        }
+    }
+
+    private void renderUncomparedOutcomes(BuildComparisonResult result, HtmlRenderContext context) {
+        renderUncomparedOutcomeSet(true, result.uncomparedSourceOutcomes, context)
+        renderUncomparedOutcomeSet(false, result.uncomparedTargetOutcomes, context)
+    }
+
+    protected void renderUncomparedOutcomeSet(boolean isSource, Set<BuildOutcome> uncompareds, HtmlRenderContext context) {
+        def side = isSource ? "source" : "target"
+        def other = isSource ? "target" : "source"
+
+        if (uncompareds) {
+            context.render {
+                h2 "Uncompared ${side} outcomes"
+                p "Uncompared ${side} build outcomes are outcomes that were not matched with a ${other} build outcome."
+
+                ol {
+                    for (uncompared in uncompareds) {
+                        li {
+                            a(href: "#${uncompared.name}", uncompared.name)
+                        }
+                    }
+                }
+
+                for (uncompared in uncompareds) {
+                    BuildOutcomeRenderer renderer = outcomeRenderers.getRenderer(uncompared.getClass())
+
+                    if (renderer == null) {
+                        throw new IllegalArgumentException(String.format("Cannot find renderer for build outcome type: %s", uncompared.getClass()))
+                    }
+
+                    div("class": "build-outcome text-container ${side}", id: uncompared.name) {
+                        renderer.render(uncompared, context)
+                    }
+                }
+            }
+        }
+    }
+
+    protected void renderOutcomeComparisons(BuildComparisonResult result, HtmlRenderContext context) {
+        context.render {
+            h2 "Compared build outcomes"
+            p "Compared build outcomes are outcomes that have been identified as being intended to be the same between the target and source build."
+
+            ol {
+                for (comparison in result.comparisons) {
+                    li {
+                        // TODO: assuming that the names are unique and that they are always the same on both sides which they are in 1.2
+                        a("class": context.diffClass(comparison.outcomesAreIdentical), href: "#${name(comparison)}", name(comparison))
+                    }
+                }
+            }
+
+            for (BuildOutcomeComparisonResult comparison in result.comparisons) {
+                BuildOutcomeComparisonResultRenderer renderer = comparisonRenderers.getRenderer(comparison.getClass())
+
+                if (renderer == null) {
+                    throw new IllegalArgumentException(String.format("Cannot find renderer for build outcome comparison result type: %s", comparison.getClass()))
+                }
+
+                div("class": "build-outcome-comparison text-container", id: name(comparison)) {
+                    renderer.render(comparison, context)
+                }
+            }
+        }
+    }
+
+    void renderHead(BuildComparisonResult result, HtmlRenderContext context) {
+        context.render {
+            title "Gradle Build Comparison"
+            meta("http-equiv": "Content-Type", content: "text/html; charset=$encoding")
+            style {
+                mkp.yieldUnescaped loadBaseCss()
+
+                mkp.yieldUnescaped """
+                    .${context.diffClass} { color: red !important; }
+                    .${context.equalClass} { color: green !important; }
+
+                    .container {
+                        padding: 30px;
+                        background-color: #FFF7F7;
+                        border-radius: 10px;
+                        border: 1px solid #E0E0E0;
+                    }
+
+                    .build-outcome-comparison, .build-outcome {
+                        padding: 10px 10px 14px 20px;
+                        border: 1px solid #D0D0D0;
+                        border-radius: 6px;
+                        margin-bottom: 1.2em;
+                    }
+
+                    .build-outcome-comparison > :last-child, .build-outcome > :last-child {
+                        margin-bottom: 0;
+                    }
+
+                    .build-outcome-comparison > :first-child, .build-outcome > :first-child {
+                         margin-top: 0;
+                    }
+
+                    .warning {
+                        background-color: #FFEFF2;
+                        color: red;
+                        border: 1px solid #FFCBCB;
+                        padding: 6px;
+                        border-radius: 6px;
+                        display: inline-block;
+                    }
+
+                    .warning > :last-child {
+                        margin-bottom: 0;
+                    }
+                """
+            }
+        }
+    }
+
+    void renderHeading(BuildComparisonResult result, HtmlRenderContext context) {
+        context.render {
+            h1 "Gradle Build Comparison"
+
+            p(id: "overall-result", "class": context.equalOrDiffClass(result.buildsAreIdentical)) {
+                b result.buildsAreIdentical ?
+                    "The build outcomes were found to be identical." :
+                    "The build outcomes were not found to be identical."
+            }
+
+            div(id: "host-details") {
+                h2 "Comparison host details"
+                def lines = hostAttributes.collect {
+                    "<strong>${StringEscapeUtils.escapeHtml(it.key)}</strong>: ${StringEscapeUtils.escapeHtml(it.value) }"
+                }
+                p {
+                    mkp.yieldUnescaped lines.join("<br />")
+                }
+            }
+
+            def sourceBuild = sourceExecuter.spec
+            def targetBuild = targetExecuter.spec
+
+            div(id: "compared-builds") {
+                h2 "Compared builds"
+
+                // We are reaching pretty deep into the knowledge of how things were created.
+                // This is not great and should be fixed.
+                if (!sourceExecuter.canObtainProjectOutcomesModel) {
+                    inferredOutcomesWarningMessage(true, sourceExecuter, context)
+                }
+                if (!targetExecuter.canObtainProjectOutcomesModel) {
+                    inferredOutcomesWarningMessage(false, targetExecuter, context)
+                }
+
+                table {
+                    tr {
+                        th class: "border-right", ""
+                        th "Source Build"
+                        th "Target Build"
+                    }
+
+                    def sourceBuildPath = sourceBuild.projectDir.absolutePath
+                    def targetBuildPath = targetBuild.projectDir.absolutePath
+                    tr("class": context.diffClass(sourceBuildPath == targetBuildPath)) {
+                        th class: "border-right no-border-bottom", "Project"
+                        td sourceBuildPath
+                        td targetBuildPath
+                    }
+
+                    def sourceGradleVersion = sourceBuild.gradleVersion
+                    def targetGradleVersion = targetBuild.gradleVersion
+                    tr("class": context.diffClass(sourceGradleVersion == targetGradleVersion)) {
+                        th class: "border-right no-border-bottom", "Gradle version"
+                        td sourceGradleVersion
+                        td targetGradleVersion
+                    }
+
+                    def sourceBuildTasks = sourceBuild.tasks
+                    def targetBuildTasks = targetBuild.tasks
+                    tr("class": context.diffClass(sourceBuildTasks == targetBuildTasks)) {
+                        th class: "border-right no-border-bottom", "Tasks"
+                        td sourceBuildTasks.join(" ")
+                        td targetBuildTasks.join(" ")
+                    }
+
+                    def sourceBuildArguments = sourceBuild.arguments
+                    def targetBuildArguments = targetBuild.arguments
+                    tr("class": context.diffClass(sourceBuildArguments == targetBuildArguments)) {
+                        th class: "border-right no-border-bottom", "Arguments"
+                        td sourceBuildArguments.join(" ")
+                        td targetBuildArguments.join(" ")
+                    }
+                }
+            }
+        }
+    }
+
+    protected void inferredOutcomesWarningMessage(boolean isSourceBuild, ComparableGradleBuildExecuter executer, HtmlRenderContext context) {
+        def inferredFor = isSourceBuild ? "source" : "target"
+        def inferredFrom = isSourceBuild ? "target" : "source"
+
+        context.render {
+            div(class: "warning inferred-outcomes para") {
+                p "Build outcomes were not able to be determined for the ${inferredFor} build as Gradle ${executer.spec.gradleVersion} does not support this feature."
+                p "The outcomes for this build have inferred from the ${inferredFrom} build. That is, it is assumed to produce the same outcomes as the ${inferredFrom} build."
+                p "This may result in a less accurate comparison."
+            }
+        }
+    }
+
+    String name(BuildOutcomeComparisonResult<?> comparisonResult) {
+        comparisonResult.compared.source.name
+    }
+
+    private String loadBaseCss() {
+        URL resource = getClass().getResource("base.css")
+        if (resource) {
+            resource.getText("utf8")
+        } else {
+            /*
+                The file is generated during the build and added to the classpath by the processResources task
+                I don't want to make this fatal as it could break developer test runs in the IDE.
+                TODO - add a distribution test to make sure the file is there
+            */
+
+            " /* base css not found at runtime */ "
+        }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/HtmlRenderContext.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/HtmlRenderContext.java
new file mode 100644
index 0000000..68dd3e3
--- /dev/null
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/HtmlRenderContext.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.api.plugins.buildcomparison.render.internal.html;
+
+import groovy.lang.Closure;
+import groovy.xml.MarkupBuilder;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.gradle.api.Transformer;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+
+public class HtmlRenderContext {
+
+    private final MarkupBuilder markupBuilder;
+    private final Transformer<String, File> relativizer;
+
+    public HtmlRenderContext(MarkupBuilder markupBuilder, Transformer<String, File> relativizer) {
+        this.relativizer = relativizer;
+        this.markupBuilder = markupBuilder;
+    }
+
+    public void render(Closure<?> renderAction) {
+        ConfigureUtil.configure(renderAction, markupBuilder);
+    }
+
+    public String relativePath(File file) {
+        return relativizer.transform(file);
+    }
+
+    public String getDiffClass() {
+        return "diff";
+    }
+
+    public String getEqualClass() {
+        return "equal";
+    }
+
+    public String getComparisonResultMsgClass() {
+        return "comparison-result-msg";
+    }
+
+    public String diffClass(Object isEqual) {
+        return groovyTrue(isEqual) ? "" : getDiffClass();
+    }
+
+    public String equalClass(Object isEqual) {
+        return groovyTrue(isEqual) ? getEqualClass() : "";
+    }
+
+    public String equalOrDiffClass(Object isEqual) {
+        return groovyTrue(isEqual) ? getEqualClass() : getDiffClass();
+    }
+
+    private boolean groovyTrue(Object equal) {
+        return (Boolean) InvokerHelper.invokeMethod(equal, "asBoolean", null);
+    }
+
+}
diff --git a/subprojects/build-comparison/src/main/resources/META-INF/gradle-plugins/compare-gradle-builds.properties b/subprojects/build-comparison/src/main/resources/META-INF/gradle-plugins/compare-gradle-builds.properties
new file mode 100644
index 0000000..7ac120f
--- /dev/null
+++ b/subprojects/build-comparison/src/main/resources/META-INF/gradle-plugins/compare-gradle-builds.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuildsPlugin
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactoryTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactoryTest.groovy
new file mode 100644
index 0000000..df6caba
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/BuildComparisonSpecFactoryTest.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.plugins.buildcomparison.compare.internal
+
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.ByTypeAndNameBuildOutcomeAssociator
+import spock.lang.Specification
+
+class BuildComparisonSpecFactoryTest extends Specification {
+
+    def "build spec"() {
+        given:
+        def factory = new BuildComparisonSpecFactory(new ByTypeAndNameBuildOutcomeAssociator(StringBuildOutcome))
+
+        when:
+        def result = factory.createSpec(strs("a", "b", "c"), strs("b", "c", "d"))
+
+        then:
+        result.source == strs("a", "b", "c")
+        result.target == strs("b", "c", "d")
+        result.outcomeAssociations.size() == 2
+        def associations = result.outcomeAssociations.toList().sort { it.source.name }
+
+        associations[0].source == str("b")
+        associations[0].target == str("b")
+        associations[0].type == StringBuildOutcome
+
+        associations[1].source == str("c")
+        associations[1].target == str("c")
+        associations[1].type == StringBuildOutcome
+    }
+
+    Set<BuildOutcome> strs(String... strings) {
+        strings.collect { str(it) }
+    }
+
+    BuildOutcome str(String str) {
+        new StringBuildOutcome(str, str)
+    }
+
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparatorTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparatorTest.groovy
new file mode 100644
index 0000000..f9d2676
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildComparatorTest.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.api.plugins.buildcomparison.compare.internal
+
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeComparator
+
+import spock.lang.Specification
+
+import static org.apache.commons.lang.StringUtils.getLevenshteinDistance
+
+class DefaultBuildComparatorTest extends Specification {
+
+    def outcomeComparatorFactory = new DefaultBuildOutcomeComparatorFactory()
+    def buildComparator = new DefaultBuildComparator(outcomeComparatorFactory)
+    def specBuilder = new DefaultBuildComparisonSpecBuilder()
+
+    def setup() {
+        outcomeComparatorFactory.registerComparator(new StringBuildOutcomeComparator())
+    }
+
+    def "simple comparison"() {
+        when:
+        associateStrings("abc", "abd")
+        associateStrings("123", "123")
+
+        then:
+        def results = compareBuilds()
+        results.comparisons.size() == 2
+        results.comparisons[0].distance == distance("abc", "abd")
+        results.comparisons[1].distance == 0
+    }
+
+    def "comparison with unassociateds"() {
+        when:
+        def uncomparedFrom = toStringOutcome("a")
+        def uncomparedTo = toStringOutcome("a")
+        specBuilder.addUnassociatedFrom(uncomparedFrom)
+        specBuilder.addUnassociatedTo(uncomparedTo)
+        associateStrings("c", "c")
+
+        then:
+        def results = compareBuilds()
+        results.comparisons.first().distance == 0
+        results.uncomparedSourceOutcomes == [uncomparedFrom] as Set
+        results.uncomparedTargetOutcomes == [uncomparedTo] as Set
+    }
+
+    def "empty spec is ok"() {
+        expect:
+        def result = compareBuilds()
+        result.comparisons.empty
+        result.uncomparedSourceOutcomes.empty
+        result.uncomparedTargetOutcomes.empty
+    }
+
+    protected int distance(String from, String to) {
+        getLevenshteinDistance(from, to)
+    }
+
+    protected BuildComparisonResult compareBuilds() {
+        buildComparator.compareBuilds(specBuilder.build())
+    }
+
+    void associateStrings(BuildComparisonSpecBuilder builder = specBuilder, from, to) {
+        builder.associate(toStringOutcome(from), toStringOutcome(to), StringBuildOutcome)
+    }
+
+    StringBuildOutcome toStringOutcome(name, value = name) {
+        new StringBuildOutcome(name, value)
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactoryTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactoryTest.groovy
new file mode 100644
index 0000000..22b519b
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/compare/internal/DefaultBuildOutcomeComparatorFactoryTest.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.api.plugins.buildcomparison.compare.internal
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation
+import spock.lang.Specification
+
+class DefaultBuildOutcomeComparatorFactoryTest extends Specification {
+
+    def factory = new DefaultBuildOutcomeComparatorFactory()
+
+    private static class BuildOutcome1 implements BuildOutcome {
+        String getName() { "1" }
+        String getDescription() { "1" }
+    }
+
+    private static class BuildOutcome2 implements BuildOutcome {
+        String getName() { "2" }
+        String getDescription() { "2" }
+    }
+
+    def buildOutcome1 = new BuildOutcome1()
+    def buildOutcome2 = new BuildOutcome2()
+
+    def comparator1 = toBuildOutcomeComparator(buildOutcome1)
+    def comparator2 = toBuildOutcomeComparator(buildOutcome2)
+
+    def "can register comparator"() {
+        expect:
+        factory.getComparator(buildOutcome1.class) == null
+
+        when:
+        factory.registerComparator(comparator1)
+
+        then:
+        factory.getComparator(buildOutcome1.class) == comparator1
+    }
+
+    def "comparators match registration"() {
+        when:
+        factory.registerComparator(comparator1)
+        factory.registerComparator(comparator2)
+
+        then:
+        factory.getComparator(buildOutcome1.class) == comparator1
+        factory.getComparator(buildOutcome2.class) == comparator2
+    }
+
+    def "registering for existing replaces"() {
+        given:
+        factory.registerComparator(comparator1)
+
+        expect:
+        factory.getComparator(buildOutcome1.class) == comparator1
+
+        when:
+        def replacement = toBuildOutcomeComparator(buildOutcome1)
+        factory.registerComparator(replacement)
+
+        then:
+        factory.getComparator(buildOutcome1.class) == replacement
+    }
+
+    private static BuildOutcomeComparator toBuildOutcomeComparator(BuildOutcome buildOutcome) {
+        new BuildOutcomeComparator<>() {
+            String toString() {
+                "comparator for $buildOutcome"
+            }
+
+            Class getComparedType() {
+                return buildOutcome.class
+            }
+
+            BuildOutcomeComparisonResult compare(BuildOutcomeAssociation association) {
+                return null
+            }
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpecTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpecTest.groovy
new file mode 100644
index 0000000..e64df57
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/DefaultGradleBuildInvocationSpecTest.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.buildcomparison.gradle.internal
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class DefaultGradleBuildInvocationSpecTest extends Specification {
+
+    FileResolver fileResolver = HelperUtil.createRootProject().fileResolver
+
+    def "equals and hashCode"() {
+        given:
+        def left = spec()
+        def right = spec()
+
+        expect:
+        left == right
+        left.hashCode() == right.hashCode()
+
+        when:
+        left.tasks = ["a"]
+
+        then:
+        left != right
+    }
+
+    protected DefaultGradleBuildInvocationSpec spec() {
+        new DefaultGradleBuildInvocationSpec(fileResolver, ".")
+    }
+
+    def "gradle version is validated"() {
+        when:
+        spec().gradleVersion = "1.1"
+
+        then:
+        notThrown Exception
+
+        when:
+        spec().gradleVersion = "sadfasd"
+
+        then:
+        thrown IllegalArgumentException
+    }
+
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy
new file mode 100644
index 0000000..c1b0779
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetInferrerTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.gradle.internal
+
+import org.gradle.api.internal.filestore.PathNormalisingKeyFileStore
+import org.gradle.api.plugins.buildcomparison.fixtures.ProjectOutcomesBuilder
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
+
+class GradleBuildOutcomeSetInferrerTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+
+    def inferBase = dir.createDir("infer-base")
+    def store = new PathNormalisingKeyFileStore(dir.createDir("fs"))
+    def transformer = new GradleBuildOutcomeSetTransformer(store, "source")
+    def inferrer = new GradleBuildOutcomeSetInferrer(store, "target", inferBase)
+
+    def "can infer"() {
+        given:
+        ProjectOutcomesBuilder builder = new ProjectOutcomesBuilder()
+        ProjectOutcomes projectOutput = builder.build("root", dir.createDir("source")) {
+            createChild("a") {
+                addFile "a1", JAR_ARTIFACT.typeIdentifier
+            }
+            createChild("b") {
+                addFile "b1", ZIP_ARTIFACT.typeIdentifier
+                addFile "b2", EAR_ARTIFACT.typeIdentifier
+            }
+            createChild("c") {
+                createChild("a") {
+                    addFile "ca1", WAR_ARTIFACT.typeIdentifier
+                    addFile "ca2", TAR_ARTIFACT.typeIdentifier
+                }
+            }
+            createChild("d")
+        }
+
+        when:
+        allBuildOutcomes(projectOutput).findAll { it instanceof GradleFileBuildOutcome }.each {
+            new TestFile(it.file) << it.name
+        }
+        def outcomes = transformer.transform(projectOutput)
+
+        outcomes.findAll { it instanceof GeneratedArchiveBuildOutcome }.each {
+            inferBase.createFile(it.rootRelativePath)
+        }
+
+        and:
+        def inferred = inferrer.transform(outcomes).collectEntries { [it.name, it] }
+
+        then:
+        inferred.size() == 5
+        inferred.keySet().toList().sort() == [":a:a1", ":b:b1", ":b:b2", ":c:a:ca1", ":c:a:ca2"]
+        inferred[":a:a1"] instanceof GeneratedArchiveBuildOutcome
+        inferred[":a:a1"].archiveFile.name == "a1"
+        inferred[":b:b1"] instanceof GeneratedArchiveBuildOutcome
+        inferred[":b:b1"].archiveFile.name == "b1"
+        inferred[":b:b2"] instanceof GeneratedArchiveBuildOutcome
+        inferred[":c:a:ca1"] instanceof GeneratedArchiveBuildOutcome
+        inferred[":c:a:ca2"] instanceof UnknownBuildOutcome
+    }
+
+    List<GradleBuildOutcome> allBuildOutcomes(ProjectOutcomes outcomes, List<GradleBuildOutcome> collector = []) {
+        collector.addAll(outcomes.outcomes)
+        for (child in outcomes.children) {
+            allBuildOutcomes(child, collector)
+        }
+        collector
+    }
+
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy
new file mode 100644
index 0000000..0fc35f6
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildOutcomeSetTransformerTest.groovy
@@ -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.api.plugins.buildcomparison.gradle.internal
+
+import org.gradle.api.internal.filestore.FileStore
+import org.gradle.api.internal.filestore.FileStoreEntry
+import org.gradle.api.plugins.buildcomparison.fixtures.ProjectOutcomesBuilder
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.unknown.UnknownBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import spock.lang.Specification
+
+import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
+import org.gradle.api.internal.filestore.AbstractFileStoreEntry
+import org.gradle.util.TestFile
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
+import org.gradle.api.Action
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+class GradleBuildOutcomeSetTransformerTest extends Specification {
+
+    def store = new FileStore<String>() {
+        FileStoreEntry move(String key, File source) {
+            new AbstractFileStoreEntry() {
+                File getFile() {
+                    source
+                }
+            }
+        }
+
+        FileStoreEntry copy(String key, File source) {
+            new AbstractFileStoreEntry() {
+                File getFile() {
+                    source
+                }
+            }
+        }
+
+        void moveFilestore(File destination) {
+            throw new UnsupportedOperationException()
+        }
+
+        FileStoreEntry add(String key, Action<File> addAction) {
+            throw new UnsupportedOperationException()
+        }
+    }
+
+    def transformer = new GradleBuildOutcomeSetTransformer(store, "things")
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+
+    def "can transform"() {
+        given:
+        ProjectOutcomesBuilder builder = new ProjectOutcomesBuilder()
+        ProjectOutcomes projectOutput = builder.build(dir.dir) {
+            createChild("a") {
+                addFile "a1", JAR_ARTIFACT.typeIdentifier
+            }
+            createChild("b") {
+                addFile "b1", ZIP_ARTIFACT.typeIdentifier
+                addFile "b2", EAR_ARTIFACT.typeIdentifier
+            }
+            createChild("c") {
+                createChild("a") {
+                    addFile "ca1", WAR_ARTIFACT.typeIdentifier
+                    addFile "ca2", TAR_ARTIFACT.typeIdentifier
+                }
+            }
+            createChild("d")
+        }
+
+        when:
+        allBuildOutcomes(projectOutput).findAll { it instanceof GradleFileBuildOutcome }.each {
+            new TestFile(it.file) << it.name
+        }
+        def outcomes = transformer.transform(projectOutput).collectEntries { [it.name, it] }
+
+        then:
+        outcomes.size() == 5
+        outcomes.keySet().toList().sort() == [":a:a1", ":b:b1", ":b:b2", ":c:a:ca1", ":c:a:ca2"]
+        outcomes[":a:a1"] instanceof GeneratedArchiveBuildOutcome
+        outcomes[":a:a1"].archiveFile.name == "a1"
+        outcomes[":b:b1"] instanceof GeneratedArchiveBuildOutcome
+        outcomes[":b:b1"].archiveFile.name == "b1"
+        outcomes[":b:b2"] instanceof GeneratedArchiveBuildOutcome
+        outcomes[":c:a:ca1"] instanceof GeneratedArchiveBuildOutcome
+        outcomes[":c:a:ca2"] instanceof UnknownBuildOutcome
+    }
+
+    List<GradleBuildOutcome> allBuildOutcomes(ProjectOutcomes outcomes, List<GradleBuildOutcome> collector = []) {
+        collector.addAll(outcomes.outcomes)
+        for (child in outcomes.children) {
+            allBuildOutcomes(child, collector)
+        }
+        collector
+    }
+
+}
+
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociatorTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociatorTest.groovy
new file mode 100644
index 0000000..62bc365
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/ByTypeAndNameBuildOutcomeAssociatorTest.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.plugins.buildcomparison.outcome.internal
+
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcome
+
+import spock.lang.Specification
+
+class ByTypeAndNameBuildOutcomeAssociatorTest extends Specification {
+
+    def associator = new ByTypeAndNameBuildOutcomeAssociator(StringBuildOutcome)
+
+    def "associates"(BuildOutcome from, BuildOutcome to, boolean match) {
+        expect:
+        associator.findAssociationType(from, to) == (match ? StringBuildOutcome : null)
+
+        where:
+        from     | to                               | match
+        str("a") | str("a")                         | true
+        str("b") | str("a")                         | false
+        str("a") | new OtherBuildOutcome(name: "a") | false
+        str("b") | new OtherBuildOutcome(name: "a") | false
+    }
+
+    StringBuildOutcome str(String str) {
+        new StringBuildOutcome(str, str)
+    }
+
+    static class OtherBuildOutcome implements BuildOutcome {
+        String name
+        String description
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy
new file mode 100644
index 0000000..2328c70
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/GeneratedArchiveBuildOutcomeComparatorTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.outcome.internal.archive
+
+import org.gradle.api.Transformer
+import org.gradle.api.internal.filestore.AbstractFileStoreEntry
+import org.gradle.api.plugins.buildcomparison.outcome.internal.DefaultBuildOutcomeAssociation
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntry
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.entry.ArchiveEntryComparison
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.api.plugins.buildcomparison.compare.internal.ComparisonResultType.*
+
+class GeneratedArchiveBuildOutcomeComparatorTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+
+    def transformer = Mock(Transformer)
+    def comparator = new GeneratedArchiveBuildOutcomeComparator(transformer)
+
+    def existingFrom = outcome("from")
+    def existingTo = outcome("to")
+
+    protected DefaultBuildOutcomeAssociation associate(from, to) {
+        new DefaultBuildOutcomeAssociation(from, to, GeneratedArchiveBuildOutcome)
+    }
+
+    ArchiveEntry entry(Map attrs) {
+        new ArchiveEntry(attrs)
+    }
+
+    void mockEntries(File archive, ArchiveEntry... entries) {
+        interaction {
+            _ * transformer.transform(archive) >>> [entries as Set]
+        }
+    }
+
+    def "compare entries"() {
+        when:
+        mockEntries(existingFrom.archiveFile,
+                entry(path: "f1"),
+                entry(path: "d1/"),
+                entry(path: "d1/f1", size: 10), // diff
+                entry(path: "d1/f2"), // only in from
+                entry(path: "d2/"), // only in from
+                entry(path: "f2") // only in from
+        )
+
+        and:
+        mockEntries(existingTo.archiveFile,
+                entry(path: "f1"),
+                entry(path: "d1/"),
+                entry(path: "d1/f1", size: 20),
+                entry(path: "d1/f3"), // only in to
+                entry(path: "d3/"), // only in to
+                entry(path: "f3") // only in to
+        )
+
+        then:
+        def result = compare(existingFrom, existingTo)
+        result.entryComparisons*.path == ["d1/", "d1/f1", "d1/f2", "d1/f3", "d2/", "d3/", "f1", "f2", "f3"]
+        Map<String, ArchiveEntryComparison> indexed = result.entryComparisons.collectEntries { [it.path, it] }
+
+        indexed["f1"].comparisonResultType == EQUAL
+        indexed["f2"].comparisonResultType == SOURCE_ONLY
+        indexed["f3"].comparisonResultType == TARGET_ONLY
+
+        indexed["d1/"].comparisonResultType == EQUAL
+        indexed["d1/f1"].comparisonResultType == UNEQUAL
+        indexed["d1/f2"].comparisonResultType == SOURCE_ONLY
+        indexed["d1/f3"].comparisonResultType == TARGET_ONLY
+
+        indexed["d2/"].comparisonResultType == SOURCE_ONLY
+        indexed["d3/"].comparisonResultType == TARGET_ONLY
+    }
+
+
+    def "comparison result types"() {
+        given:
+        def unequal = outcome("unequal")
+        def notExistingFrom = outcome("no-from", null)
+        def notExistingTo = outcome("no-to", null)
+
+        mockEntries(existingFrom.archiveFile, entry(path: "f1"))
+        mockEntries(existingTo.archiveFile, entry(path: "f1"))
+        mockEntries(unequal.archiveFile, entry(path: "f1", size: 20))
+
+        expect:
+        compare(existingFrom, existingTo).comparisonResultType == EQUAL
+        compare(notExistingFrom, existingTo).comparisonResultType == TARGET_ONLY
+        compare(existingFrom, notExistingTo).comparisonResultType == SOURCE_ONLY
+        compare(existingFrom, unequal).comparisonResultType == UNEQUAL
+        compare(notExistingFrom, notExistingTo).comparisonResultType == NON_EXISTENT
+
+        and:
+        compare(existingFrom, existingTo).outcomesAreIdentical
+        !compare(notExistingFrom, existingTo).outcomesAreIdentical
+        !compare(existingFrom, notExistingTo).outcomesAreIdentical
+        !compare(existingFrom, unequal).outcomesAreIdentical
+        !compare(notExistingFrom, notExistingTo).outcomesAreIdentical
+    }
+
+    protected GeneratedArchiveBuildOutcomeComparisonResult compare(from, to) {
+        comparator.compare(associate(from, to))
+    }
+
+    GeneratedArchiveBuildOutcome outcome(String name, File file = dir.createFile(name)) {
+        def fileStoreEntry = new AbstractFileStoreEntry() {
+            File getFile() { file }
+        }
+        new GeneratedArchiveBuildOutcome(name, name, fileStoreEntry, name)
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparisonTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparisonTest.groovy
new file mode 100644
index 0000000..a2e5dc3
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryComparisonTest.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.api.plugins.buildcomparison.outcome.internal.archive.entry
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.ComparisonResultType
+import spock.lang.Specification
+
+class ArchiveEntryComparisonTest extends Specification {
+
+    ArchiveEntry entry(Map attrs) {
+        new ArchiveEntry(attrs)
+    }
+
+    def path
+    def from = entry(path: path, size: 10)
+    def to = entry(path: path, size: 10)
+
+    ArchiveEntryComparison comparison(String path = path, ArchiveEntry from = from, ArchiveEntry to = to) {
+        new ArchiveEntryComparison(path, from, to)
+    }
+
+
+    def "comparisons"() {
+        expect:
+        comparison().comparisonResultType == ComparisonResultType.EQUAL
+
+        when:
+        from.size += 1
+
+        then:
+        comparison().comparisonResultType == ComparisonResultType.UNEQUAL
+
+        when:
+        from = null
+
+        then:
+        comparison().comparisonResultType == ComparisonResultType.TARGET_ONLY
+
+        when:
+        from = to
+        to = null
+
+        then:
+        comparison().comparisonResultType == ComparisonResultType.SOURCE_ONLY
+    }
+
+    def "from or to must be null"() {
+        given:
+        from = null
+        to = null
+
+        when:
+        comparison()
+
+        then:
+        thrown IllegalArgumentException
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryTest.groovy
new file mode 100644
index 0000000..74b5717
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ArchiveEntryTest.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.api.plugins.buildcomparison.outcome.internal.archive.entry
+
+import spock.lang.Specification
+
+class ArchiveEntryTest extends Specification {
+
+    def "equals and hash code"() {
+        when:
+        def a1 = new ArchiveEntry(
+                path: "foo",
+                size: 10,
+                crc: 10,
+                directory: true
+        )
+        def a2 = new ArchiveEntry(
+                path: "foo",
+                size: 10,
+                crc: 10,
+                directory: true
+        )
+
+        then:
+        a1 == a2
+        a2 == a1
+        a1.hashCode() == a2.hashCode()
+
+        when:
+        a2.size = 20
+
+        then:
+        a1 != a2
+        a2 != a1
+        a1.hashCode() != a2.hashCode()
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.groovy
new file mode 100644
index 0000000..a3cf521
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/FileToArchiveEntrySetTransformerTest.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.api.plugins.buildcomparison.outcome.internal.archive.entry
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class FileToArchiveEntrySetTransformerTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+    Set<ArchiveEntry> entrySet
+
+    def transformer = new FileToArchiveEntrySetTransformer(new ZipEntryToArchiveEntryTransformer())
+
+    TestFile contents
+    TestFile zip
+
+    def setup() {
+        contents = dir.createDir("contents")
+        zip = dir.file("zip.zip")
+    }
+
+    def createZip(Closure c) {
+        contents.with(c)
+        contents.zipTo(zip)
+        entrySet = transformer.transform(zip)
+    }
+
+    def "can handle zip file"() {
+        when:
+        createZip {
+            createFile("f1.txt") << "f1"
+            createFile("dir1/f2.txt") << "f2"
+            createFile("dir1/f3.txt") << "f3"
+            createFile("dir2/f4.txt") << "f3"
+        }
+
+        then:
+        entrySet.size() == 6
+        entrySet*.path.sort() == ["dir1/", "dir1/f2.txt", "dir1/f3.txt", "dir2/", "dir2/f4.txt", "f1.txt"]
+    }
+
+    def "can handle empty zip"() {
+        when:
+        createZip { }
+
+        then:
+        entrySet.empty
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformerTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformerTest.groovy
new file mode 100644
index 0000000..d63b62e
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/outcome/internal/archive/entry/ZipEntryToArchiveEntryTransformerTest.groovy
@@ -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.plugins.buildcomparison.outcome.internal.archive.entry
+
+import spock.lang.Specification
+
+import java.util.zip.ZipEntry
+
+class ZipEntryToArchiveEntryTransformerTest extends Specification {
+
+    def transformer = new ZipEntryToArchiveEntryTransformer()
+
+    def "transforms file"() {
+        given:
+        def zip = zipEntry("abc") {
+            size = 10
+            crc = 10
+        }
+
+        when:
+        def archiveEntry = transformer.transform(zip)
+
+        then:
+        archiveEntry.path == "abc"
+        archiveEntry.crc == 10
+        !archiveEntry.directory
+        archiveEntry.size == 10
+    }
+
+    def "transforms directory"() {
+        given:
+        def zip = zipEntry("abc/") { }
+
+        when:
+        def archiveEntry = transformer.transform(zip)
+
+        then:
+        archiveEntry.path == "abc/"
+        archiveEntry.crc == -1
+        archiveEntry.directory
+        archiveEntry.size == -1
+    }
+
+    ZipEntry zipEntry(String name, Closure c) {
+        def ze = new ZipEntry(name)
+        ze.with(c)
+        ze
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactoryTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactoryTest.groovy
new file mode 100644
index 0000000..c4935a2
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/DefaultBuildOutcomeComparisonResultRendererFactoryTest.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.api.plugins.buildcomparison.render.internal
+
+import spock.lang.Specification
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeComparisonResultMapRenderer
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeComparisonResult
+import org.gradle.api.plugins.buildcomparison.outcome.internal.archive.GeneratedArchiveBuildOutcomeComparisonResult
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult
+
+class DefaultBuildOutcomeComparisonResultRendererFactoryTest extends Specification {
+
+    def contextType = Map
+    def factory = createFactory()
+
+    DefaultBuildOutcomeComparisonResultRendererFactory createFactory(contextType = this.contextType) {
+        new DefaultBuildOutcomeComparisonResultRendererFactory(contextType)
+    }
+
+    def "can register and retrieve"() {
+        when:
+        def renderer = new StringBuildOutcomeComparisonResultMapRenderer()
+        factory.registerRenderer(renderer)
+
+        then:
+        factory.getRenderer(StringBuildOutcomeComparisonResult) == renderer
+        factory.getRenderer(GeneratedArchiveBuildOutcomeComparisonResult) == null
+    }
+
+    def "registration requires the same context type"() {
+        given:
+        def renderer = new BuildOutcomeComparisonResultRenderer() {
+            Class getResultType() { StringBuildOutcomeComparisonResult }
+            Class getContextType() { List }
+            void render(BuildOutcomeComparisonResult result, Object context) {}
+        }
+
+        when:
+        factory.registerRenderer(renderer)
+
+        then:
+        thrown IllegalArgumentException
+    }
+}
diff --git a/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy
new file mode 100644
index 0000000..57e2fca
--- /dev/null
+++ b/subprojects/build-comparison/src/test/groovy/org/gradle/api/plugins/buildcomparison/render/internal/html/GradleBuildComparisonResultHtmlRendererTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.buildcomparison.render.internal.html
+
+import org.gradle.api.Transformer
+import org.gradle.api.internal.file.TestFiles
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildComparisonResult
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResult
+import org.gradle.api.plugins.buildcomparison.gradle.internal.ComparableGradleBuildExecuter
+import org.gradle.api.plugins.buildcomparison.gradle.internal.DefaultGradleBuildInvocationSpec
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.internal.DefaultBuildOutcomeAssociation
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcome
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeComparisonResult
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeComparisonResultHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.outcome.string.StringBuildOutcomeHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.BuildComparisonResultRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.DefaultBuildOutcomeComparisonResultRendererFactory
+import org.gradle.api.plugins.buildcomparison.render.internal.DefaultBuildOutcomeRendererFactory
+import org.gradle.util.TemporaryFolder
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.junit.Rule
+import spock.lang.Specification
+
+import java.nio.charset.Charset
+
+class GradleBuildComparisonResultHtmlRendererTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+
+    def comparisonRenderers = new DefaultBuildOutcomeComparisonResultRendererFactory(HtmlRenderContext)
+    def outcomeRenderers = new DefaultBuildOutcomeRendererFactory(HtmlRenderContext)
+
+    def unassociatedFrom = new HashSet<BuildOutcome>()
+    def unassociatedTo = new HashSet<BuildOutcome>()
+    def comparisons = new LinkedList<BuildOutcomeComparisonResult>()
+
+    def writer = new StringWriter()
+
+    def hostAttributes = [foo: "bar"]
+
+    def sourceBuildDir = dir.createDir("source")
+    def targetBuildDir = dir.createDir("target")
+    def resolver = TestFiles.resolver(dir.dir)
+    def sourceBuildSpec = new DefaultGradleBuildInvocationSpec(resolver, sourceBuildDir)
+    def targetBuildSpec = new DefaultGradleBuildInvocationSpec(resolver, targetBuildDir)
+    def sourceBuildExecuter = new ComparableGradleBuildExecuter(sourceBuildSpec)
+    def targetBuildExecuter = new ComparableGradleBuildExecuter(targetBuildSpec)
+
+    BuildComparisonResultRenderer makeRenderer() {
+        new GradleBuildComparisonResultHtmlRenderer(comparisonRenderers, outcomeRenderers, Charset.defaultCharset(), sourceBuildExecuter, targetBuildExecuter, hostAttributes, new Transformer() {
+            Object transform(Object original) {
+                original.path
+            }
+        })
+    }
+
+    BuildComparisonResult makeResult(
+            Set<BuildOutcome> unassociatedFrom = this.unassociatedFrom,
+            Set<BuildOutcome> unassociatedTo = this.unassociatedTo,
+            List<BuildOutcomeComparisonResult<?>> comparisons = this.comparisons
+    ) {
+        new BuildComparisonResult(unassociatedFrom, unassociatedTo, comparisons)
+    }
+
+    StringBuildOutcome str(name, value = name) {
+        new StringBuildOutcome(name, value)
+    }
+
+    Set<StringBuildOutcome> strs(String... strings) {
+        strings.collect { str(it) } as Set
+    }
+
+    BuildOutcomeComparisonResult strcmp(String from, String to) {
+        new StringBuildOutcomeComparisonResult(
+                new DefaultBuildOutcomeAssociation(str(from), str(to), StringBuildOutcome)
+        )
+    }
+
+    Document render() {
+        makeRenderer().render(makeResult(), writer)
+        Jsoup.parse(writer.toString())
+    }
+
+    def "render some results"() {
+        given:
+        comparisonRenderers.registerRenderer(new StringBuildOutcomeComparisonResultHtmlRenderer())
+        outcomeRenderers.registerRenderer(new StringBuildOutcomeHtmlRenderer())
+
+        and:
+        comparisons << strcmp("a", "a")
+        comparisons << strcmp("a", "b")
+        comparisons << strcmp("a", "c")
+
+        unassociatedFrom << str("foo")
+        unassociatedTo << str("bar")
+
+        when:
+        def html = render()
+
+        then:
+        // Just need to test that the renderers were called correctly, not the renderers themselves
+        def tables = html.select(".build-outcome-comparison table")
+        tables.size() == 3
+        tables[0].select("th").text() == "Source Target Distance"
+        tables[0].select("td")[0].text() == "a"
+        tables[2].select("td")[2].text() == comparisons.last.distance.toString()
+        html.select(".build-outcome.source").find { it.id() == "foo" }
+        html.select(".build-outcome.target").find { it.id() == "bar" }
+    }
+
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableDomainObjectSet.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableDomainObjectSet.groovy
new file mode 100644
index 0000000..1f61126
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableDomainObjectSet.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.api.plugins.buildcomparison.fixtures
+
+import org.gradle.tooling.model.DomainObjectSet
+
+class MutableDomainObjectSet extends LinkedHashSet implements DomainObjectSet {
+    List getAll() {
+        new ArrayList(this)
+    }
+
+    Object getAt(int index) {
+        getAll().getAt(index)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableProjectOutcomes.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableProjectOutcomes.groovy
new file mode 100644
index 0000000..c1b35bf
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/MutableProjectOutcomes.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.api.plugins.buildcomparison.fixtures
+
+import org.gradle.tooling.model.DomainObjectSet
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes
+import org.gradle.util.ConfigureUtil
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome
+
+class MutableProjectOutcomes implements ProjectOutcomes {
+    String name
+    String description
+    String path
+    MutableProjectOutcomes parent
+    DomainObjectSet<MutableProjectOutcomes> children = new MutableDomainObjectSet()
+    File projectDirectory
+    DomainObjectSet<? extends GradleBuildOutcome> outcomes = new MutableDomainObjectSet()
+
+    MutableProjectOutcomes createChild(String childName, Closure c = {}) {
+        def mpo = new MutableProjectOutcomes()
+        mpo.parent = this
+        mpo.name = childName
+        mpo.path = parent ? "$path:$childName" : ":$childName"
+        mpo.projectDirectory = new File(projectDirectory, childName)
+        mpo.description = "project $mpo.path"
+
+        children << mpo
+        ConfigureUtil.configure(c, mpo)
+    }
+
+    GradleFileBuildOutcome addFile(String archivePath, String typeIdentifier = null, String taskName = archivePath) {
+        def outcome = new GradleFileBuildOutcome() {
+
+            String getId() {
+                archivePath
+            }
+
+            String getDescription() {
+                "fake outcome $archivePath"
+            }
+
+            File getFile() {
+                new File(projectDirectory, archivePath)
+            }
+
+            String getTaskPath() {
+                "$path:$taskName"
+            }
+
+            String getTypeIdentifier() {
+                typeIdentifier
+            }
+        }
+        outcomes << outcome
+        outcome
+    }
+}
\ No newline at end of file
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/ProjectOutcomesBuilder.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/ProjectOutcomesBuilder.groovy
new file mode 100644
index 0000000..ba4dc91
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/fixtures/ProjectOutcomesBuilder.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.api.plugins.buildcomparison.fixtures
+
+import org.gradle.util.ConfigureUtil
+
+class ProjectOutcomesBuilder {
+
+    MutableProjectOutcomes build(String name = "root", File dir, Closure c) {
+        def root = new MutableProjectOutcomes()
+        root.name = name
+        root.description = "root project"
+        root.projectDirectory = dir
+        root.path = ":"
+
+        ConfigureUtil.configure(c, root)
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcome.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcome.groovy
new file mode 100644
index 0000000..1b85eb7
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcome.groovy
@@ -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.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcome
+
+class StringBuildOutcome implements BuildOutcome {
+
+    final String name
+    final String value
+
+    StringBuildOutcome(String name, String value) {
+        this.name = name
+        this.value = value
+    }
+
+    String getDescription() {
+        "string: $value"
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) {
+            return true
+        }
+        if (getClass() != o.class) {
+            return false
+        }
+
+        StringBuildOutcome that = (StringBuildOutcome) o
+
+        if (name != that.name) {
+            return false
+        }
+        if (value != that.value) {
+            return false
+        }
+
+        return true
+    }
+
+    int hashCode() {
+        int result
+        result = (name != null ? name.hashCode() : 0)
+        result = 31 * result + (value != null ? value.hashCode() : 0)
+        return result
+    }
+
+    @Override
+    public String toString() {
+        return "StringBuildOutcome{" +
+                "name='" + name + '\'' +
+                ", value='" + value + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparator.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparator.groovy
new file mode 100644
index 0000000..fc8769f
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparator.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.api.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparator
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation
+
+class StringBuildOutcomeComparator implements BuildOutcomeComparator<StringBuildOutcome, StringBuildOutcomeComparisonResult> {
+
+    Class<StringBuildOutcome> getComparedType() {
+        StringBuildOutcome
+    }
+
+    StringBuildOutcomeComparisonResult compare(BuildOutcomeAssociation<StringBuildOutcome> association) {
+        new StringBuildOutcomeComparisonResult(association)
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResult.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResult.groovy
new file mode 100644
index 0000000..7b93d79
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResult.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.api.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.outcome.internal.BuildOutcomeAssociation
+import org.gradle.api.plugins.buildcomparison.compare.internal.BuildOutcomeComparisonResultSupport
+
+import static org.apache.commons.lang.StringUtils.getLevenshteinDistance
+
+class StringBuildOutcomeComparisonResult extends BuildOutcomeComparisonResultSupport<StringBuildOutcome> {
+
+    StringBuildOutcomeComparisonResult(BuildOutcomeAssociation<StringBuildOutcome> compared) {
+        super(compared)
+    }
+
+    int getDistance() {
+        getLevenshteinDistance(compared.source.value, compared.target.value)
+    }
+
+    boolean isOutcomesAreIdentical() {
+        distance != 0
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultHtmlRenderer.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultHtmlRenderer.groovy
new file mode 100644
index 0000000..cee36b5
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultHtmlRenderer.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.api.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeComparisonResultHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+class StringBuildOutcomeComparisonResultHtmlRenderer extends BuildOutcomeComparisonResultHtmlRenderer<StringBuildOutcomeComparisonResult> {
+
+    Class<StringBuildOutcomeComparisonResult> getResultType() {
+        StringBuildOutcomeComparisonResult
+    }
+
+    void render(StringBuildOutcomeComparisonResult result, HtmlRenderContext context) {
+        context.render {
+            table {
+                tr {
+                    th "Source"
+                    th "Target"
+                    th "Distance"
+                }
+                tr {
+                    td result.compared.source.value
+                    td result.compared.target.value
+                    td result.distance
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultMapRenderer.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultMapRenderer.groovy
new file mode 100644
index 0000000..7e5215c
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeComparisonResultMapRenderer.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.api.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.render.internal.BuildOutcomeComparisonResultRenderer
+
+class StringBuildOutcomeComparisonResultMapRenderer implements BuildOutcomeComparisonResultRenderer<StringBuildOutcomeComparisonResult, Map> {
+
+    Class<StringBuildOutcomeComparisonResult> getResultType() {
+        StringBuildOutcomeComparisonResult
+    }
+
+    Class<Map> getContextType() {
+        Map
+    }
+
+    void render(StringBuildOutcomeComparisonResult result, Map context) {
+        context.source = result.compared.source.value
+        context.target = result.compared.target.value
+        context.distance = result.distance
+    }
+}
diff --git a/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeHtmlRenderer.groovy b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeHtmlRenderer.groovy
new file mode 100644
index 0000000..8ff5312
--- /dev/null
+++ b/subprojects/build-comparison/src/testFixtures/groovy/org/gradle/api/plugins/buildcomparison/outcome/string/StringBuildOutcomeHtmlRenderer.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.api.plugins.buildcomparison.outcome.string
+
+import org.gradle.api.plugins.buildcomparison.render.internal.html.BuildOutcomeHtmlRenderer
+import org.gradle.api.plugins.buildcomparison.render.internal.html.HtmlRenderContext
+
+class StringBuildOutcomeHtmlRenderer extends BuildOutcomeHtmlRenderer<StringBuildOutcome> {
+
+    final Class<StringBuildOutcome> outcomeType = StringBuildOutcome
+
+    void render(StringBuildOutcome outcome, HtmlRenderContext context) {
+        renderTitle(outcome, context)
+        context.render { p outcome.value }
+    }
+
+}
diff --git a/subprojects/cli/cli.gradle b/subprojects/cli/cli.gradle
index 2419010..68118d9 100644
--- a/subprojects/cli/cli.gradle
+++ b/subprojects/cli/cli.gradle
@@ -20,8 +20,8 @@
     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
+}
+
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
index 69133a2..8520081 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
@@ -25,7 +25,7 @@ public class CommandLineOption {
     private String description;
     private String subcommand;
     private String deprecationWarning;
-    private boolean experimental;
+    private boolean incubating;
 
     public CommandLineOption(Iterable<String> options) {
         for (String option : options) {
@@ -69,11 +69,11 @@ public class CommandLineOption {
             result.append(deprecationWarning);
             result.append("]");
         }
-        if (experimental) {
+        if (incubating) {
             if (result.length() > 0) {
                 result.append(' ');
             }
-            result.append("[experimental]");
+            result.append("[incubating]");
         }
         return result.toString();
     }
@@ -96,8 +96,8 @@ public class CommandLineOption {
         return this;
     }
 
-    public CommandLineOption experimental() {
-        experimental = true;
+    public CommandLineOption incubating() {
+        incubating = true;
         return this;
     }
     
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java
index a130de2..20bc9a4 100644
--- a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java
+++ b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java
@@ -22,7 +22,7 @@ public class ParsedCommandLineOption {
     private final List<String> values = new ArrayList<String>();
 
     public String getValue() {
-        if (values.isEmpty()) {
+        if (!hasValue()) {
             throw new IllegalStateException("Option does not have any value.");
         }
         if (values.size() > 1) {
@@ -38,4 +38,8 @@ public class ParsedCommandLineOption {
     public void addArgument(String argument) {
         values.add(argument);
     }
+
+    public boolean hasValue() {
+        return !values.isEmpty();
+    }
 }
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
index b3d321e..d44dbac 100644
--- a/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
@@ -354,11 +354,11 @@ class CommandLineParserTest extends Specification {
         ]
     }
     
-    def formatsUsageMessageForDeprecatedAndExperimentalOptions() {
+    def formatsUsageMessageForDeprecatedAndIncubatingOptions() {
         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()
+        parser.option('c').hasDescription('option c').incubating()
+        parser.option('d').incubating()
         def outstr = new StringWriter()
 
         expect:
@@ -366,8 +366,8 @@ class CommandLineParserTest extends Specification {
         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]'
+                '-c                 option c [incubating]',
+                '-d                 [incubating]'
         ]
     }
 
@@ -590,15 +590,34 @@ class CommandLineParserTest extends Specification {
         parser.allowUnknownOptions()
 
         when:
-        def result = parser.parse(['-a', '-b'])
+        def result = parser.parse(['-a', '-b', '--long-option'])
 
         then:
         result.option("a") != null
         
         and:
-        result.extraArguments.contains("-b")
+        result.extraArguments == ['-b', '--long-option']
     }
-    
+
+    def "allow unknown options mode collects unknown short options combined with known short options"() {
+        given:
+        parser.option("a")
+        parser.option("Z")
+
+        and:
+        parser.allowUnknownOptions()
+
+        when:
+        def result = parser.parse(['-abCdZ'])
+
+        then:
+        result.option("a") != null
+        result.option("Z") != null
+
+        and:
+        result.extraArguments == ["-b", "-C", "-d"]
+    }
+
     @Issue("http://issues.gradle.org/browse/GRADLE-1871")
     def "unknown options containing known arguments in their value are allowed"() {
         given:
@@ -614,8 +633,7 @@ class CommandLineParserTest extends Specification {
         result.option("a") != null
         
         and:
-        "-ba" in result.extraArguments
-        "-ba=c" in result.extraArguments
+        result.extraArguments == ['-ba', '-ba=c']
     }
 
 }
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy
new file mode 100644
index 0000000..224a638
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineOptionSpec.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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: 9/5/12
+ */
+class ParsedCommandLineOptionSpec extends Specification {
+
+    final option = new ParsedCommandLineOption();
+
+    def "reports no value"() {
+        when:
+        option.getValue()
+
+        then:
+        thrown(IllegalStateException)
+        !option.hasValue()
+    }
+
+    def "reports single value"() {
+        when:
+        option.addArgument("foo")
+
+        then:
+        option.hasValue()
+        "foo" == option.value
+        ["foo"] == option.values
+    }
+
+    def "reports multiple values"() {
+        when:
+        option.addArgument("foo")
+        option.addArgument("bar")
+
+        then:
+        option.hasValue()
+        ["foo", "bar"] == option.values
+
+        when:
+        option.getValue()
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
diff --git a/subprojects/code-quality/code-quality.gradle b/subprojects/code-quality/code-quality.gradle
index 509652d..90640da 100644
--- a/subprojects/code-quality/code-quality.gradle
+++ b/subprojects/code-quality/code-quality.gradle
@@ -20,13 +20,14 @@ dependencies {
 
     compile project(':core')
     compile project(':plugins')
+    compile project(':reporting')
 
     compile libraries.slf4j_api
 
     // 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 "com.google.code.findbugs:findbugs:2.0.1 at jar"
+    provided "com.google.code.findbugs:bcel:2.0.1 at jar"
     provided "dom4j:dom4j:1.6.1 at jar"
     provided "jaxen:jaxen:1.1.1 at jar"
 }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/AutoTestedSampleCodeQualityIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/AutoTestedSampleCodeQualityIntegrationTest.groovy
new file mode 100644
index 0000000..15337a5
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/AutoTestedSampleCodeQualityIntegrationTest.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.api.plugins.quality
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+
+import org.junit.Test
+
+class AutoTestedSampleCodeQualityIntegrationTest extends AbstractAutoTestedSamplesTest {
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/code-quality/src/main")
+    }
+}
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
index 83dc8bf..913f3a5 100644
--- 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
@@ -17,6 +17,7 @@ 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
@@ -46,7 +47,7 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         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"))
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
     }
 
     void "can ignore failures"() {
@@ -60,7 +61,8 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         expect:
         succeeds("check")
         output.contains("FindBugs rule violations were found. See the report at:")
-        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
+        file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.BadClassTest"))
     }
 
     def "is incremental"() {
@@ -93,7 +95,37 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         expect:
         fails "findbugsMain"
 
-        failure.assertHasCause "Findbugs tasks can only have one report enabled"
+        failure.assertHasCause "FindBugs tasks can only have one report enabled"
+    }
+
+    def "can use optional arguments"() {
+        given:
+        buildFile << """
+            findbugs {
+                effort 'max'
+                reportLevel 'high'
+                includeFilter file('include.xml')
+                excludeFilter file('exclude.xml')
+                visitors = ['FindDeadLocalStores', 'UnreadFields']
+                omitVisitors = ['WaitInLoop', 'UnnecessaryMath']
+            }
+            findbugsMain.reports {
+                xml.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+        badCode()
+
+        and:
+        writeFilterFile('include.xml', '.*')
+        writeFilterFile('exclude.xml', 'org\\.gradle\\.Bad.*')
+
+        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"))
     }
 
     def "can generate html reports"() {
@@ -147,13 +179,16 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
 
     private goodCode(int numberOfClasses = 1) {
         1.upto(numberOfClasses) {
-            file("src/main/java/org/gradle/Class${it}.java") << "package org.gradle; class Class${it} { public boolean isFoo(Object arg) { return true; } }"
-            file("src/test/java/org/gradle/Class${it}Test.java") << "package org.gradle; class Class${it}Test { public boolean isFoo(Object arg) { return true; } }"
+            file("src/main/java/org/gradle/Class${it}.java") << "package org.gradle; public class Class${it} { public boolean isFoo(Object arg) { return true; } }"
+            file("src/test/java/org/gradle/Class${it}Test.java") << "package org.gradle; public class Class${it}Test { public boolean isFoo(Object arg) { return true; } }"
         }
     }
 
     private badCode() {
-        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean equals(Object arg) { return true; } }"
+        // Has DM_EXIT
+        file('src/main/java/org/gradle/BadClass.java') << 'package org.gradle; public class BadClass { public boolean isFoo(Object arg) { System.exit(1); return true; } }'
+        // Has ES_COMPARING_PARAMETER_STRING_WITH_EQ
+        file('src/test/java/org/gradle/BadClassTest.java') << 'package org.gradle; public class BadClassTest { public boolean isFoo(Object arg) { return "true" == "false"; } }'
     }
 
     private Matcher<String> containsClass(String className) {
@@ -162,13 +197,22 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
 
     private void writeBuildFile() {
         file("build.gradle") << """
-        apply plugin: "java"
-        apply plugin: "findbugs"
-        
-        repositories {
-            mavenCentral()
-        }
+            apply plugin: "java"
+            apply plugin: "findbugs"
+
+            repositories {
+                mavenCentral()
+            }
+        """
+    }
 
+    private void writeFilterFile(String filename, String className) {
+        file(filename) << """
+            <FindBugsFilter>
+            <Match>
+                <Class name="${className}" />
+            </Match>
+            </FindBugsFilter>
         """
     }
 }
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
index 67fe949..99e2544 100644
--- 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
@@ -16,31 +16,39 @@
 
 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
+import org.gradle.util.TemporaryFolder
+
+import org.junit.Rule
+
+import spock.lang.Specification
 
 class FindBugsSpecBuilderTest extends Specification {
+    @Rule TemporaryFolder tempFolder = new TemporaryFolder()
 
     FileCollection classes = Mock()
     FindBugsSpecBuilder builder = new FindBugsSpecBuilder(classes)
 
     def setup(){
-        _ * classes.getFiles() >> Collections.emptyList()
+        classes.getFiles() >> []
     }
+
     def "fails with empty classes Collection"() {
         when:
         new FindBugsSpecBuilder(null)
+
         then:
         thrown(InvalidUserDataException)
 
         when:
-        classes.empty >> true
         new FindBugsSpecBuilder(classes)
+
         then:
+        classes.empty >> true
         thrown(InvalidUserDataException)
     }
 
@@ -49,11 +57,13 @@ class FindBugsSpecBuilderTest extends Specification {
         NamedDomainObjectSet enabledReportSet = Mock()
         FindBugsReportsImpl report = Mock()
 
-        report.enabled >> enabledReportSet;
+        report.enabled >> enabledReportSet
         enabledReportSet.empty >> true
+
         when:
         builder.configureReports(report)
         def spec = builder.build()
+
         then:
         !spec.arguments.contains("-outputFile")
     }
@@ -62,6 +72,7 @@ class FindBugsSpecBuilderTest extends Specification {
         when:
         builder.withDebugging(debug)
         def spec = builder.build()
+
         then:
         spec.debugEnabled == debug
 
@@ -73,7 +84,7 @@ class FindBugsSpecBuilderTest extends Specification {
         setup:
         NamedDomainObjectSet enabledReportSet = Mock()
         FindBugsReportsImpl report = Mock()
-        report.enabled >> enabledReportSet;
+        report.enabled >> enabledReportSet
         enabledReportSet.empty >> false
         enabledReportSet.size() >> 2
 
@@ -83,7 +94,7 @@ class FindBugsSpecBuilderTest extends Specification {
 
         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."
+        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"() {
@@ -93,7 +104,7 @@ class FindBugsSpecBuilderTest extends Specification {
         NamedDomainObjectSet enabledReportSet = Mock()
         FindBugsReportsImpl report = Mock()
 
-        report.enabled >> enabledReportSet;
+        report.enabled >> enabledReportSet
         report.firstEnabled >> singleReport
         singleReport.name >> reportType
         destination.absolutePath >> "/absolute/report/output"
@@ -112,6 +123,80 @@ class FindBugsSpecBuilderTest extends Specification {
         args.contains(destination.absolutePath)
 
         where:
-        reportType << ["xml", "html"];
+        reportType << ["xml", "html"]
+    }
+
+    def "configure effort"() {
+        when:
+        def args = builder.withEffort(effort).build().arguments
+
+        then:
+        args.contains("-effort:$effort" as String)
+
+        where:
+        effort << ["min", "default", "max"]
+    }
+
+    def "detects invalid effort value"() {
+        when:
+        builder.withEffort("unknown")
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "configure report level"() {
+        when:
+        def args = builder.withReportLevel(level).build().arguments
+
+        then:
+        args.contains("-$level" as String)
+
+        where:
+        level << ["experimental", "low", "medium", "high"]
+    }
+
+    def "detects invalid report level value"() {
+        when:
+        builder.withReportLevel("unknown")
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "configure visitors"() {
+        when:
+        def args = builder.withVisitors(["Foo", "Bar"]).build().arguments.join(" ")
+
+        then:
+        args.contains("-visitors Foo,Bar")
+    }
+
+    def "configure omitVisitors"() {
+        when:
+        def args = builder.withOmitVisitors(["Foo", "Bar"]).build().arguments.join(" ")
+
+        then:
+        args.contains("-omitVisitors Foo,Bar")
+    }
+
+    def "configure includeFilter"() {
+        def file = tempFolder.createFile("include.txt")
+
+        when:
+        def args = builder.withIncludeFilter(file).build().arguments.join(" ")
+
+        then:
+        args.contains("-include $file")
+    }
+
+    def "configure excludeFilter"() {
+        def file = tempFolder.createFile("exclude.txt")
+
+        when:
+        def args = builder.withExcludeFilter(file).build().arguments.join(" ")
+
+        then:
+        args.contains("-exclude $file")
     }
 }
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
index b4468b0..af18000 100644
--- 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
@@ -25,6 +25,8 @@ import org.gradle.internal.reflect.Instantiator
 import org.gradle.logging.ConsoleRenderer
 import org.gradle.util.DeprecationLogger
 
+import javax.inject.Inject
+
 /**
  * Runs Checkstyle against some source files.
  */
@@ -58,7 +60,7 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
     /**
      * The properties available for use in the configuration file. These are substituted into the configuration
      * file.
-     * 
+     *
      * @deprecated renamed to <tt>configProperties</tt>
      */
     @Deprecated
@@ -80,7 +82,15 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
     }
 
     @Nested
-    private final CheckstyleReportsImpl reports = services.get(Instantiator).newInstance(CheckstyleReportsImpl, this)
+    private final CheckstyleReportsImpl reports
+
+    private final IsolatedAntBuilder antBuilder
+
+    @Inject
+    Checkstyle(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
+        this.antBuilder = antBuilder
+        reports = instantiator.newInstance(CheckstyleReportsImpl, this)
+    }
 
     /**
      * The reports to be generated by this task.
@@ -141,7 +151,6 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
     @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')
 
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
index edf1a53..4c95f83 100644
--- 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
@@ -27,6 +27,8 @@ import org.gradle.internal.reflect.Instantiator
 import org.gradle.logging.ConsoleRenderer
 import org.gradle.util.DeprecationLogger
 
+import javax.inject.Inject
+
 /**
  * Runs CodeNarc against some source files.
  */
@@ -86,17 +88,24 @@ class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNar
     }
 
     @Nested
-    private final CodeNarcReportsImpl reports = services.get(Instantiator).newInstance(CodeNarcReportsImpl, this)
+    private final CodeNarcReportsImpl reports
+
+    private final IsolatedAntBuilder antBuilder
 
     /**
      * Whether or not the build should break when the verifications performed by this task fail.
      */
     boolean ignoreFailures
 
+    @Inject
+    CodeNarc(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
+        reports = instantiator.newInstance(CodeNarcReportsImpl, this)
+        this.antBuilder = antBuilder
+    }
+
     @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 {
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
index 7de554e..d712977 100644
--- 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
@@ -25,11 +25,19 @@ import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
 import org.gradle.api.reporting.Reporting
 import org.gradle.api.tasks.*
 import org.gradle.api.logging.LogLevel
+import org.gradle.internal.Factory
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.logging.ConsoleRenderer
+import org.gradle.process.internal.WorkerProcessBuilder
+
+import groovy.transform.PackageScope
+
+import javax.inject.Inject
 
 /**
- * Analyzes code with <a href="http://findbugs.sourceforge.net">FindBugs</a>.
+ * Analyzes code with <a href="http://findbugs.sourceforge.net">FindBugs</a>. See the
+ * <a href="http://findbugs.sourceforge.net/manual/">FindBugs Manual</a> for additional information
+ * on configuration options.
  */
 class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBugsReports> {
     /**
@@ -64,8 +72,63 @@ class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBug
      */
     boolean ignoreFailures
 
+    /**
+     * The analysis effort level. The value specified should be one of {@code min}, {@code default}, or {@code max}.
+     * Higher levels increase precision and find more bugs at the expense of running time and memory consumption.
+     */
+    @Input
+    @Optional
+    String effort
+
+    /**
+     * The priority threshold for reporting bugs. If set to {@code low}, all bugs are reported. If set to
+     * {@code medium} (the default), medium and high priority bugs are reported. If set to {@code high},
+     * only high priority bugs are reported.
+     */
+    @Input
+    @Optional
+    String reportLevel
+
+    /**
+     * The bug detectors which should be run. The bug detectors are specified by their class names,
+     * without any package qualification. By default, all detectors which are not disabled by default are run.
+     */
+    @Input
+    @Optional
+    Collection<String> visitors = []
+
+    /**
+     * Similar to {@code visitors} except that it specifies bug detectors which should not be run.
+     * By default, no visitors are omitted.
+     */
+    @Input
+    @Optional
+    Collection<String> omitVisitors = []
+
+    /**
+     * The filename of a filter specifying which bugs are reported.
+     */
+    @InputFile
+    @Optional
+    File includeFilter
+
+    /**
+     * The filename of a filter specifying bugs to exclude from being reported.
+     */
+    @InputFile
+    @Optional
+    File excludeFilter
+
     @Nested
-    private final FindBugsReportsImpl reports = services.get(Instantiator).newInstance(FindBugsReportsImpl, this)
+    private final FindBugsReportsImpl reports
+
+    private final Factory<WorkerProcessBuilder> workerFactory
+
+    @Inject
+    FindBugs(Instantiator instantiator, Factory<WorkerProcessBuilder> workerFactory) {
+        reports = instantiator.newInstance(FindBugsReportsImpl, this)
+        this.workerFactory = workerFactory
+    }
 
     /**
      * The reports to be generated by this task.
@@ -100,31 +163,49 @@ class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBug
 
     @TaskAction
     void run() {
-        FindBugsSpecBuilder argumentBuilder = new FindBugsSpecBuilder(getClasses())
-                .withPluginsList(getPluginClasspath())
-                .withSources(getSource())
-                .withClasspath(getClasspath())
-                .withDebugging(logger.isDebugEnabled())
-                .configureReports(reports)
-
-        FindBugsSpec spec = argumentBuilder.build()
-        FindBugsWorkerManager manager = new FindBugsWorkerManager();
+        FindBugsSpec spec = generateSpec()
+        FindBugsWorkerManager manager = new FindBugsWorkerManager()
 
         logging.captureStandardOutput(LogLevel.DEBUG)
         logging.captureStandardError(LogLevel.DEBUG)
 
-        FindBugsResult findbugsResult = manager.runWorker(getProject(), getFindbugsClasspath(), spec)
-        evaluateResult(findbugsResult);
+        FindBugsResult result = manager.runWorker(getProject().getProjectDir(), workerFactory, getFindbugsClasspath(), spec)
+        evaluateResult(result);
     }
 
-    void evaluateResult(FindBugsResult findbugsResult) {
-        if (findbugsResult.exception){
-            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.", findbugsResult.exception)
+    /**
+     * For testing only.
+     */
+    @PackageScope
+    FindBugsSpec generateSpec() {
+        FindBugsSpecBuilder specBuilder = new FindBugsSpecBuilder(getClasses())
+            .withPluginsList(getPluginClasspath())
+            .withSources(getSource())
+            .withClasspath(getClasspath())
+            .withDebugging(logger.isDebugEnabled())
+            .withEffort(getEffort())
+            .withReportLevel(getReportLevel())
+            .withVisitors(getVisitors())
+            .withOmitVisitors(getOmitVisitors())
+            .withExcludeFilter(getExcludeFilter())
+            .withIncludeFilter(getIncludeFilter())
+            .configureReports(getReports())
+
+        return specBuilder.build()
+    }
+
+    /**
+     * For testing only.
+     */
+    @PackageScope
+    void evaluateResult(FindBugsResult result) {
+        if (result.exception) {
+            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.", result.exception)
         }
-        if (findbugsResult.errorCount){
+        if (result.errorCount) {
             throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.")
         }
-        if (findbugsResult.bugCount) {
+        if (result.bugCount) {
             def message = "FindBugs rule violations were found."
             def report = reports.firstEnabled
             if (report) {
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
index 32574a7..2355bb9 100644
--- 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
@@ -16,9 +16,66 @@
 package org.gradle.api.plugins.quality
 
 /**
- * Configuration options for the FindBugs plugin.
+ * Configuration options for the FindBugs plugin. All options have sensible defaults.
+ * See the <a href="http://findbugs.sourceforge.net/manual/">FindBugs Manual</a> for additional information
+ * on these options.
+ *
+ * <p>Below is a full configuration example. Since all properties have sensible defaults,
+ * typically only selected properties will be configured.
+ *
+ * <pre autoTested=''>
+ *     apply plugin: "java"
+ *     apply plugin: "findbugs"
+ *
+ *     findbugs {
+ *         toolVersion = "2.0.1"
+ *         sourceSets = [sourceSets.main]
+ *         ignoreFailures = true
+ *         reportsDir = file("$project.buildDir/findbugsReports")
+ *         effort = "max"
+ *         reportLevel = "high"
+ *         visitors = ["FindSqlInjection", "SwitchFallthrough"]
+ *         omitVisitors = ["FindNonShortCircuit"]
+ *         includeFilter = file("$rootProject.projectDir/config/findbugs/includeFilter.xml")
+ *         excludeFilter = file("$rootProject.projectDir/config/findbugs/excludeFilter.xml")
+ *     }
+ * </pre>
  *
  * @see FindBugsPlugin
  */
 class FindBugsExtension extends CodeQualityExtension {
+    /**
+     * The analysis effort level. The value specified should be one of {@code min}, {@code default}, or {@code max}.
+     * Higher levels increase precision and find more bugs at the expense of running time and memory consumption.
+     */
+    String effort
+
+    /**
+     * The priority threshold for reporting bugs. If set to {@code low}, all bugs are reported. If set to
+     * {@code medium} (the default), medium and high priority bugs are reported. If set to {@code high},
+     * only high priority bugs are reported.
+     */
+    String reportLevel
+
+    /**
+     * The bug detectors which should be run. The bug detectors are specified by their class names,
+     * without any package qualification. By default, all detectors which are not disabled by default are run.
+     */
+    Collection<String> visitors
+
+    /**
+     * Similar to {@code visitors} except that it specifies bug detectors which should not be run.
+     * By default, no visitors are omitted.
+     */
+    Collection<String> omitVisitors
+
+    /**
+     * The filename of a filter specifying which bugs are reported.
+     */
+    File includeFilter
+
+    /**
+     * The filename of a filter specifying bugs to exclude from being reported.
+     */
+    File excludeFilter
 }
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
index 67b7eab..b38df0a 100644
--- 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
@@ -63,7 +63,7 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
     protected CodeQualityExtension createExtension() {
         extension = project.extensions.create("findbugs", FindBugsExtension)
         extension.with {
-            toolVersion = "2.0.0"
+            toolVersion = "2.0.1"
         }
         return extension
     }
@@ -84,6 +84,13 @@ class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
                 config
             }
             ignoreFailures = { extension.ignoreFailures }
+            effort = { extension.effort }
+            reportLevel = { extension.reportLevel }
+            visitors = { extension.visitors }
+            omitVisitors = { extension.omitVisitors }
+            excludeFilter = { extension.excludeFilter }
+            includeFilter = { extension.includeFilter }
+ 
         }
         task.reports.all { Report report ->
             report.conventionMapping.with {
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
index 0dac435..e20f8a7 100644
--- 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
@@ -24,6 +24,8 @@ import org.gradle.api.plugins.quality.internal.JDependReportsImpl
 import org.gradle.api.reporting.Reporting
 import org.gradle.api.tasks.*
 
+import javax.inject.Inject
+
 /**
  * Analyzes code with <a href="http://clarkware.com/software/JDepend.html">JDepend</a>.
  */
@@ -50,7 +52,15 @@ class JDepend extends DefaultTask implements Reporting<JDependReports> {
     }
 
     @Nested
-    private final JDependReportsImpl reports = services.get(Instantiator).newInstance(JDependReportsImpl, this)
+    private final JDependReportsImpl reports
+
+    private final IsolatedAntBuilder antBuilder
+
+    @Inject
+    JDepend(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
+        this.antBuilder = antBuilder
+        reports = instantiator.newInstance(JDependReportsImpl, this)
+    }
 
     /**
      * The reports to be generated by this task.
@@ -95,7 +105,6 @@ class JDepend extends DefaultTask implements Reporting<JDependReports> {
             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) {
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
index 54cf166..57101d2 100644
--- 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
@@ -24,6 +24,8 @@ import org.gradle.api.tasks.*
 import org.gradle.logging.ConsoleRenderer
 import org.gradle.api.GradleException
 
+import javax.inject.Inject
+
 /**
  * Runs a set of static code analysis rules on Java source code files and
  * generates a report of problems found.
@@ -57,6 +59,8 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
     @Nested
     private final PmdReportsImpl reports = services.get(Instantiator).newInstance(PmdReportsImpl, this)
 
+    private final IsolatedAntBuilder antBuilder
+
     /**
      * Whether or not to allow the build to continue if there are warnings.
      *
@@ -64,9 +68,14 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
      */
     boolean ignoreFailures
 
+    @Inject
+    Pmd(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
+        reports = instantiator.newInstance(PmdReportsImpl, this)
+        this.antBuilder = antBuilder
+    }
+
     @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: false, failuresPropertyName: "pmdFailureCount") {
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
index d5a331d..7447001 100644
--- 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
@@ -16,13 +16,14 @@
 
 package org.gradle.api.plugins.quality.internal.findbugs;
 
+import com.google.common.base.Objects;
+
 import java.io.Serializable;
 import java.util.List;
 
 public class FindBugsSpec implements Serializable {
-    private boolean debugEnabled;
-
     private List<String> arguments;
+    private boolean debugEnabled;
 
     public FindBugsSpec(List<String> arguments, boolean debugEnabled) {
         this.debugEnabled = debugEnabled;
@@ -38,17 +39,6 @@ public class FindBugsSpec implements Serializable {
     }
 
     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();
+        return Objects.toStringHelper(this).add("arguments", arguments).add("debugEnabled", debugEnabled).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
index 0e26e3e..df59ff2 100644
--- 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
@@ -16,29 +16,40 @@
 
 package org.gradle.api.plugins.quality.internal.findbugs;
 
+import com.google.common.collect.ImmutableSet;
 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 org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
 
 public class FindBugsSpecBuilder {
+    private static final Set<String> VALID_EFFORTS = ImmutableSet.of("min", "default", "max");
+    private static final Set<String> VALID_REPORT_LEVELS = ImmutableSet.of("experimental", "low", "medium", "high");
+
     private FileCollection pluginsList;
     private FileCollection sources;
     private FileCollection classpath;
-
-    private ArrayList<String> args;
     private FileCollection classes;
     private FindBugsReports reports;
 
+    private String effort;
+    private String reportLevel;
+    private Collection<String> visitors;
+    private Collection<String> omitVisitors;
+    private File excludeFilter;
+    private File includeFilter;
     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.");
+            throw new InvalidUserDataException("No classes configured for FindBugs analysis.");
         }
         this.classes = classes;
     }
@@ -58,18 +69,65 @@ public class FindBugsSpecBuilder {
         return this;
     }
 
-    public FindBugsSpecBuilder configureReports(FindBugsReports reports){
+    public FindBugsSpecBuilder configureReports(FindBugsReports reports) {
         this.reports = reports;
         return this;
     }
 
+
+    public FindBugsSpecBuilder withEffort(String effort) {
+        if (effort != null && !VALID_EFFORTS.contains(effort)) {
+            throw new InvalidUserDataException("Invalid value for FindBugs 'effort' property: " + effort);
+        }
+        this.effort = effort;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withReportLevel(String reportLevel) {
+        if (reportLevel != null && !VALID_REPORT_LEVELS.contains(reportLevel)) {
+            throw new InvalidUserDataException("Invalid value for FindBugs 'reportLevel' property: " + reportLevel);
+        }
+        this.reportLevel = reportLevel;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withVisitors(Collection<String> visitors) {
+        this.visitors = visitors;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withOmitVisitors(Collection<String> omitVisitors) {
+        this.omitVisitors = omitVisitors;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withExcludeFilter(File excludeFilter) {
+        if (excludeFilter != null && !excludeFilter.canRead()) {
+            String errorStr = String.format("Cannot read file specified for FindBugs 'excludeFilter' property: %s", excludeFilter);
+            throw new InvalidUserDataException(errorStr);
+        }
+
+        this.excludeFilter = excludeFilter;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withIncludeFilter(File includeFilter) {
+        if (includeFilter != null && !includeFilter.canRead()) {
+            String errorStr = String.format("Cannot read file specified for FindBugs 'includeFilter' property: %s", includeFilter);
+            throw new InvalidUserDataException(errorStr);
+        }
+
+        this.includeFilter = includeFilter;
+        return this;
+    }
+
     public FindBugsSpecBuilder withDebugging(boolean debugEnabled){
         this.debugEnabled = debugEnabled;
         return this;
     }
 
     public FindBugsSpec build() {
-        args = new ArrayList<String>();
+        ArrayList<String> args = new ArrayList<String>();
         args.add("-pluginList");
         args.add(pluginsList==null ? "" : pluginsList.getAsPath());
         args.add("-sortByClass");
@@ -83,7 +141,7 @@ public class FindBugsSpecBuilder {
                 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.");
+                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.");
             }
         }
 
@@ -95,18 +153,59 @@ public class FindBugsSpecBuilder {
         if (has(classpath)) {
             args.add("-auxclasspath");
 
-            // Filter unexisting files as findbugs can't handle them.
+            // 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());
         }
+
+        if (has(effort)) {
+            args.add(String.format("-effort:%s", effort));
+        }
+
+        if (has(reportLevel)) {
+            args.add(String.format("-%s", reportLevel));
+        }
+
+        if (has(visitors)) {
+            args.add("-visitors");
+            args.add(CollectionUtils.join(",", visitors));
+        }
+
+        if (has(omitVisitors)) {
+            args.add("-omitVisitors");
+            args.add(CollectionUtils.join(",", omitVisitors));
+        }
+
+        if (has(excludeFilter)) {
+            args.add("-exclude");
+            args.add(excludeFilter.getPath());
+        }
+
+        if (has(includeFilter)) {
+            args.add("-include");
+            args.add(includeFilter.getPath());
+        }
+
         for (File classFile : classes.getFiles()) {
             args.add(classFile.getAbsolutePath());
         }
-        FindBugsSpec spec = new FindBugsSpec(args, debugEnabled);
-        return spec;
+
+        return new FindBugsSpec(args, debugEnabled);
+    }
+
+    private boolean has(String str) {
+        return str != null && str.length() > 0;
+    }
+
+    private boolean has(File file) {
+        return file != null && file.canRead();
+    }
+
+    private boolean has(Collection<?> collection) {
+        return collection != null && !collection.isEmpty();
     }
 
     private boolean has(FileCollection fileCollection) {
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
index 09825ff..7e52b3e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
@@ -16,14 +16,14 @@
 package org.gradle.api.plugins.quality.internal.findbugs
 
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.Factory
 import org.gradle.process.internal.JavaExecHandleBuilder
 import org.gradle.process.internal.WorkerProcess
 import org.gradle.process.internal.WorkerProcessBuilder
 
 class FindBugsWorkerManager {
-    public FindBugsResult runWorker(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
-        WorkerProcess process = createWorkerProcess(project, findBugsClasspath, spec);
+    public FindBugsResult runWorker(File workingDir, Factory<WorkerProcessBuilder> workerFactory, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        WorkerProcess process = createWorkerProcess(workingDir, workerFactory, findBugsClasspath, spec);
         process.start();
 
         FindBugsWorkerClient clientCallBack = new FindBugsWorkerClient()
@@ -34,13 +34,12 @@ class FindBugsWorkerManager {
         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
+    private WorkerProcess createWorkerProcess(File workingDir, Factory<WorkerProcessBuilder> workerFactory, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        WorkerProcessBuilder builder = workerFactory.create();
+        builder.applicationClasspath(findBugsClasspath);
         builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
         JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
-        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
+        javaCommand.setWorkingDir(workingDir);
 
         WorkerProcess process = builder.worker(new FindBugsWorkerServer(spec)).build()
         return process
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java
index 12e899d..ff0a641 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java
@@ -38,7 +38,7 @@ public class FindBugsWorkerServer implements Action<WorkerProcessContext>, Seria
     }
 
     public FindBugsResult execute() {
-        LOGGER.debug("Executing findbugs worker.");
+        LOGGER.debug("Executing FindBugs worker.");
         try {
             FindBugsExecuter findBugsExecuter = new FindBugsExecuter();
             return findBugsExecuter.runFindbugs(spec);
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
index 367cc22..b983769 100644
--- 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
@@ -20,7 +20,9 @@ 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
@@ -37,7 +39,7 @@ class FindBugsPluginTest extends Specification {
         project.plugins.hasPlugin(ReportingBasePlugin)
     }
 
-    def "configures findbugs configuration"() {
+    def "configures FindBugs configuration"() {
         def config = project.configurations.findByName("findbugs")
 
         expect:
@@ -47,14 +49,20 @@ class FindBugsPluginTest extends Specification {
         config.description == 'The FindBugs libraries to be used for this project.'
     }
 
-    def "configures findbugs extension"() {
+    def "configures FindBugs extension"() {
         expect:
         FindBugsExtension extension = project.extensions.findbugs
         extension.reportsDir == project.file("build/reports/findbugs")
         !extension.ignoreFailures
+        extension.effort == null
+        extension.reportLevel == null
+        extension.visitors == null
+        extension.omitVisitors == null
+        extension.includeFilter == null
+        extension.excludeFilter == null
     }
 
-    def "configures findbugs task for each source set"() {
+    def "configures FindBugs task for each source set"() {
         project.plugins.apply(JavaBasePlugin)
         project.sourceSets {
             main
@@ -77,7 +85,13 @@ class FindBugsPluginTest extends Specification {
             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
+            assert !ignoreFailures
+            assert effort == null
+            assert reportLevel == null
+            assert visitors == null
+            assert omitVisitors == null
+            assert excludeFilter == null
+            assert includeFilter == null
         }
     }
 
@@ -92,10 +106,16 @@ class FindBugsPluginTest extends Specification {
         task.findbugsClasspath == project.configurations.findbugs
         task.pluginClasspath == project.configurations.findbugsPlugins
         task.reports.xml.destination == project.file("build/reports/findbugs/custom.xml")
-        task.ignoreFailures == false
+        !task.ignoreFailures
+        task.effort == null
+        task.reportLevel == null
+        task.visitors == null
+        task.omitVisitors == null
+        task.excludeFilter == null
+        task.includeFilter == null
     }
 
-    def "adds findbugs tasks to check lifecycle task"() {
+    def "adds FindBugs tasks to check lifecycle task"() {
         project.plugins.apply(JavaBasePlugin)
         project.sourceSets {
             main
@@ -119,6 +139,12 @@ class FindBugsPluginTest extends Specification {
             sourceSets = [project.sourceSets.main]
             reportsDir = project.file("findbugs-reports")
             ignoreFailures = true
+            effort = 'min'
+            reportLevel = 'high'
+            visitors = ['org.gradle.Class']
+            omitVisitors = ['org.gradle.Interface']
+            includeFilter = new File("include.txt")
+            excludeFilter = new File("exclude.txt")
         }
 
         expect:
@@ -137,7 +163,13 @@ class FindBugsPluginTest extends Specification {
             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
+            assert ignoreFailures
+            assert effort == 'min'
+            assert reportLevel == 'high'
+            assert visitors == ['org.gradle.Class']
+            assert omitVisitors == ['org.gradle.Interface']
+            assert includeFilter == new File("include.txt")
+            assert excludeFilter == new File("exclude.txt")
         }
     }
     
@@ -146,6 +178,12 @@ class FindBugsPluginTest extends Specification {
         project.findbugs {
             reportsDir = project.file("findbugs-reports")
             ignoreFailures = true
+            effort = 'min'
+            reportLevel = 'high'
+            visitors = ['org.gradle.Class']
+            omitVisitors = ['org.gradle.Interface']
+            includeFilter = new File("include.txt")
+            excludeFilter = new File("exclude.txt")
         }
 
         expect:
@@ -156,7 +194,13 @@ class FindBugsPluginTest extends Specification {
         task.findbugsClasspath == project.configurations.findbugs
         task.pluginClasspath == project.configurations.findbugsPlugins
         task.reports.xml.destination == project.file("findbugs-reports/custom.xml")
-        task.ignoreFailures == true
+        task.ignoreFailures
+        task.effort == 'min'
+        task.reportLevel == 'high'
+        task.visitors == ['org.gradle.Class']
+        task.omitVisitors == ['org.gradle.Interface']
+        task.includeFilter == new File("include.txt")
+        task.excludeFilter == new File("exclude.txt")
     }
 
     def "can configure reporting"() {
@@ -175,6 +219,6 @@ class FindBugsPluginTest extends Specification {
         }
 
         then:
-        notThrown()
+        noExceptionThrown()
     }
 }
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
index f2102fe..1c3077e 100644
--- 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
@@ -20,52 +20,67 @@ 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 project = ProjectBuilder.builder().build()
+        findbugs = project.tasks.add("findbugs", FindBugs)
     }
 
-    def "errorCount > 0 causes failing FindBugsTask"() {
-        setup:
-        FindBugsResult result = Mock(FindBugsResult)
+    def "fails when errorCount greater than zero"() {
+        def 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)
+    def "fails when bugCount greater than zero"() {
+        def 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:
+    def "fails when errorCount greater than zero and ignoreFailures is true"() {
+        def 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."
+    }
+
+    def "does not fail when bugCount greater than zero and ignoreFailures is true"() {
+        def result = Mock(FindBugsResult)
+        result.bugCount >> 1
+        findbugs.ignoreFailures = true
+
+        when:
         findbugs.evaluateResult(result)
+
+        then:
+        noExceptionThrown()
     }
 
-    def "task error message refer to findbugs reports if reports are configured"() {
-        setup:
-        FindBugsResult result = Mock(FindBugsResult)
+    def "error message refers to report if report is configured"() {
+        def result = Mock(FindBugsResult)
         result.bugCount >> 1
         findbugs.reports {
             xml {
@@ -73,34 +88,23 @@ class FindBugsTest extends Specification {
                 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."
+        e.message.startsWith("FindBugs rule violations were found. See the report at:")
     }
 
-    /**
-     * keep behaviour in sync with findbugs ant task
-     * */
-    def "missingClassCount > 0 does not cause failing FindBugsTask"() {
-        setup:
-        FindBugsResult result = Mock(FindBugsResult)
+    def "does not fail when missingClassCount greater than zero (keeps behavior in sync with Ant task)"() {
+        def result = Mock(FindBugsResult)
         result.missingClassCount >> 1
 
-        expect:
-        findbugs.evaluateResult(result);
+        when:
+        findbugs.evaluateResult(result)
+
+        then:
+        noExceptionThrown()
     }
 }
diff --git a/subprojects/core-impl/core-impl.gradle b/subprojects/core-impl/core-impl.gradle
index 0a551e7..af31644 100644
--- a/subprojects/core-impl/core-impl.gradle
+++ b/subprojects/core-impl/core-impl.gradle
@@ -1,6 +1,6 @@
 apply plugin: "groovy"
 
-import org.gradle.build.JarJar
+import org.gradle.build.*
 
 configurations {
     mvn3Input
@@ -18,30 +18,54 @@ dependencies {
     compile libraries.slf4j_api
     compile libraries.maven_ant_tasks
     compile libraries.nekohtml
+    runtime libraries.xbean //maven3 classes dependency
 
     testCompile libraries.junit
 
-    compile files(["$buildDir/libs/jarjar/jarjar-maven-settings-3.0.4.jar",
-            "$buildDir/libs/jarjar/jarjar-maven-settings-builder-3.0.4.jar",
-            "$buildDir/libs/jarjar/jarjar-plexus-cipher-1.4.jar",
-            "$buildDir/libs/jarjar/jarjar-plexus-component-annotations-1.5.5.jar",
-            "$buildDir/libs/jarjar/jarjar-plexus-interpolation-1.14.jar",
-            "$buildDir/libs/jarjar/jarjar-plexus-sec-dispatcher-1.3.jar",
-            "$buildDir/libs/jarjar/jarjar-plexus-utils-2.0.6.jar"]) {
-        builtBy tasks.withType(JarJar)
+    compile fileTree("$buildDir/libs/jarjar") {
+        builtBy 'jarJarMaven3'
     }
 
-    mvn3Input libraries.maven3_settings_builder
+    mvn3Input libraries.maven3
 }
-configurations.mvn3Input.files.each{libFile->
-   task "jarjar-${libFile.name}"(type: JarJar) {
-        inputFile = libFile
-        outputFile = file("$buildDir/libs/jarjar/jarjar-${libFile.name}")
-        rule('org.**', 'jarjar.org. at 1')
-    }
+
+task jarJarMaven3(type: JarJar) {
+    inputJars = configurations.mvn3Input
+    outputDir = file("$buildDir/libs/jarjar")
+
+    //unfortunately, all those need to be jarjarred.
+    // Even if some library (like aether) is not included in maven-ant-tasks it has
+    // META-INF/plexus/components.xml that to jarjarred components.
+    rule('org.apache.maven.**', 'org.gradle.mvn3.org.apache.maven. at 1')
+    rule('org.codehaus.**', 'org.gradle.mvn3.org.codehaus. at 1')
+    rule('org.sonatype.**', 'org.gradle.mvn3.org.sonatype. at 1')
+
+    avoidConflictingPlexusComponents(it)
 }
 
+classpathManifest.dependsOn jarJarMaven3 //see GRADLE-2521
+
+//adding explicit task dependencies due to http://issues.gradle.org/browse/GRADLE-2481
 def allJarJars = tasks.withType(JarJar)
-ideaModule.dependsOn allJarJars // I expected that buildable file collections links the ideaModule to the allJarJars already.
+ideaModule.dependsOn allJarJars
+eclipseClasspath.dependsOn allJarJars
+useTestFixtures()
 
-useTestFixtures()
\ No newline at end of file
+def avoidConflictingPlexusComponents(JarJar task) {
+    //DefaultSecDispatcher component is configured in 2 different jars (META-INF/plexus/components.xml).
+    //The implementation is the same but the 'hint' is different and this makes plexus fail to start.
+    //I'm removing the components.xml file from the sec-dispatcher jar.
+    //This file contains only single component so I think we can remove it.
+    task.doLast {
+        def plexusSec = "$outputDir/jarjar-plexus-sec-dispatcher-1.3.jar"
+        def plexusSecNoComps = "$plexusSec-noComps"
+        ant {
+            zip(destfile: plexusSecNoComps, update: true) {
+                zipfileset(src: plexusSec) {
+                    exclude(name: 'META-INF/plexus/components.xml')
+                }
+            }
+            move(file: plexusSecNoComps, tofile: plexusSec)
+        }
+    }
+}
\ No newline at end of file
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 2758889..1357ebf 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
@@ -17,19 +17,18 @@ package org.gradle.api.internal.artifacts;
 
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.StartParameter;
+import org.gradle.api.Transformer;
 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.internal.reflect.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.configurations.ResolverProvider;
 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.*;
 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;
@@ -44,10 +43,10 @@ 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.artifacts.mvnsettings.*;
+import org.gradle.api.internal.artifacts.repositories.DefaultBaseRepositoryFactory;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.DownloadingRepositoryCacheManager;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
 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;
@@ -55,13 +54,16 @@ import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFi
 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.file.TmpDirTemporaryFileProvider;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
+import org.gradle.api.internal.filestore.UniquePathKeyFileStore;
 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.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
@@ -203,12 +205,12 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         );
     }
 
-    protected UniquePathFileStore createUniquePathFileStore() {
-        return new UniquePathFileStore(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "filestore"));
+    protected PathKeyFileStore createUniquePathFileStore() {
+        return new UniquePathKeyFileStore(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "filestore"));
     }
 
     protected ArtifactRevisionIdFileStore createArtifactRevisionIdFileStore() {
-        return new ArtifactRevisionIdFileStore(get(UniquePathFileStore.class));
+        return new ArtifactRevisionIdFileStore(get(PathKeyFileStore.class), new TmpDirTemporaryFileProvider());
     }
 
     protected SettingsConverter createSettingsConverter() {
@@ -223,20 +225,37 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         return new DefaultIvyFactory();
     }
 
+    protected MavenSettingsProvider createMavenSettingsProvider() {
+        return new DefaultMavenSettingsProvider(new DefaultMavenFileLocations());
+    }
+
     protected LocalMavenRepositoryLocator createLocalMavenRepositoryLocator() {
-        return new DefaultLocalMavenRepositoryLocator(new DefaultMavenFileLocations(), SystemProperties.asMap(), System.getenv());
+        return new DefaultLocalMavenRepositoryLocator(get(MavenSettingsProvider.class), 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 LocalFileRepositoryCacheManager createLocalRepositoryCacheManager() {
+        return new LocalFileRepositoryCacheManager("local");
+    }
+
+    protected DownloadingRepositoryCacheManager createDownloadingRepositoryCacheManager() {
+        return new DownloadingRepositoryCacheManager("downloading", get(ArtifactRevisionIdFileStore.class), get(ByUrlCachedExternalResourceIndex.class),
+                new TmpDirTemporaryFileProvider(), get(CacheLockingManager.class));
+    }
+
     protected RepositoryTransportFactory createRepositoryTransportFactory() {
         return new RepositoryTransportFactory(
-                get(ProgressLoggerFactory.class), get(ArtifactRevisionIdFileStore.class), get(ByUrlCachedExternalResourceIndex.class)
+                get(ProgressLoggerFactory.class),
+                get(LocalFileRepositoryCacheManager.class),
+                get(DownloadingRepositoryCacheManager.class),
+                new TmpDirTemporaryFileProvider(),
+                get(ByUrlCachedExternalResourceIndex.class)
         );
     }
 
@@ -246,11 +265,12 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         private final DependencyMetaDataProvider dependencyMetaDataProvider;
         private final ProjectFinder projectFinder;
         private final DomainObjectContext domainObjectContext;
+        private RepositoryFactoryInternal repositoryFactory;
         private DefaultRepositoryHandler repositoryHandler;
         private ConfigurationContainerInternal configurationContainer;
         private DependencyHandler dependencyHandler;
         private DefaultArtifactHandler artifactHandler;
-        private ResolverFactory resolverFactory;
+        private BaseRepositoryFactory baseRepositoryFactory;
 
         private DefaultDependencyResolutionServices(ServiceRegistry parent, FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
             this.parent = parent;
@@ -267,26 +287,40 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
             return repositoryHandler;
         }
 
-        public ResolverFactory getResolverFactory() {
-            if (resolverFactory == null) {
+        public RepositoryFactoryInternal getRepositoryFactory() {
+            if (repositoryFactory == null) {
+                repositoryFactory = new DefaultRepositoryFactory(getBaseRepositoryFactory());
+            }
+            return repositoryFactory;
+        }
+
+        public BaseRepositoryFactory getBaseRepositoryFactory() {
+            if (baseRepositoryFactory == null) {
                 Instantiator instantiator = parent.get(Instantiator.class);
                 //noinspection unchecked
-                resolverFactory = new DefaultResolverFactory(
+                baseRepositoryFactory = new DefaultBaseRepositoryFactory(
                         get(LocalMavenRepositoryLocator.class),
                         fileResolver,
                         instantiator,
                         get(RepositoryTransportFactory.class),
                         get(LocallyAvailableResourceFinder.class),
-                        get(ByUrlCachedExternalResourceIndex.class)
+                        get(ProgressLoggerFactory.class),
+                        get(LocalFileRepositoryCacheManager.class),
+                        get(DownloadingRepositoryCacheManager.class),
+                        new DefaultArtifactPublisherFactory(new Transformer<ArtifactPublisher, ResolverProvider>() {
+                            public ArtifactPublisher transform(ResolverProvider resolverProvider) {
+                                return createArtifactPublisher(resolverProvider);
+                            }
+                        })
                 );
             }
 
-            return resolverFactory;
+            return baseRepositoryFactory;
         }
 
         private DefaultRepositoryHandler createRepositoryHandler() {
             Instantiator instantiator = parent.get(Instantiator.class);
-            return instantiator.newInstance(DefaultRepositoryHandler.class, getResolverFactory(), instantiator);
+            return instantiator.newInstance(DefaultRepositoryHandler.class, getRepositoryFactory(), instantiator);
         }
 
         public ConfigurationContainerInternal getConfigurationContainer() {
@@ -359,7 +393,7 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                                             resolver))));
         }
 
-        ArtifactPublisher createArtifactPublisher(DefaultRepositoryHandler resolverProvider) {
+        ArtifactPublisher createArtifactPublisher(ResolverProvider resolverProvider) {
             PublishModuleDescriptorConverter fileModuleDescriptorConverter = new PublishModuleDescriptorConverter(
                     get(ResolveModuleDescriptorConverter.class),
                     new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
@@ -371,7 +405,8 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                             get(PublishModuleDescriptorConverter.class),
                             fileModuleDescriptorConverter,
                             get(IvyFactory.class),
-                            new DefaultIvyDependencyPublisher()));
+                            new DefaultIvyDependencyPublisher(),
+                            new IvyXmlModuleDescriptorWriter()));
         }
     }
 
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
index 15f5b61..02cea03 100644
--- 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
@@ -24,24 +24,37 @@ import org.gradle.internal.Factory;
 import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultResolvedArtifact implements ResolvedArtifact {
     private final ResolvedDependency resolvedDependency;
-    private final Artifact artifact;
+    private final Map<String, String> extraAttributes;
+    private final String name;
+    private final String type;
+    private final String ext;
     private Factory<File> artifactSource;
     private File file;
 
     public DefaultResolvedArtifact(ResolvedDependency resolvedDependency, Artifact artifact, Factory<File> artifactSource) {
         this.resolvedDependency = resolvedDependency;
-        this.artifact = artifact;
+        // Unpack the stuff that we're interested from the artifact and discard. The artifact instance drags in a whole pile of stuff that
+        // we don't want to retain references to.
+        this.name = artifact.getName();
+        this.type = artifact.getType();
+        this.ext = artifact.getExt();
+        this.extraAttributes = new HashMap<String, String>(artifact.getQualifiedExtraAttributes());
         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()");
+        DeprecationLogger.nagUserOfDeprecated(
+                "ResolvedArtifact.getResolvedDependency()",
+                "For version info use ResolvedArtifact.getModuleVersion(), to access the dependency graph use ResolvedConfiguration.getFirstLevelModuleDependencies()"
+        );
         return resolvedDependency;
     }
 
@@ -75,7 +88,7 @@ public class DefaultResolvedArtifact implements ResolvedArtifact {
         if (!other.getExtension().equals(getExtension())) {
             return false;
         }
-        if (!other.artifact.getExtraAttributes().equals(artifact.getExtraAttributes())) {
+        if (!other.extraAttributes.equals(extraAttributes)) {
             return false;
         }
         return true;
@@ -83,23 +96,23 @@ public class DefaultResolvedArtifact implements ResolvedArtifact {
 
     @Override
     public int hashCode() {
-        return resolvedDependency.getModule().getId().hashCode() ^ getName().hashCode() ^ getType().hashCode() ^ getExtension().hashCode() ^ artifact.getExtraAttributes().hashCode();
+        return resolvedDependency.getModule().getId().hashCode() ^ getName().hashCode() ^ getType().hashCode() ^ getExtension().hashCode() ^ extraAttributes.hashCode();
     }
 
     public String getName() {
-        return artifact.getName();
+        return name;
     }
 
     public String getType() {
-        return artifact.getType();
+        return type;
     }
 
     public String getExtension() {
-        return artifact.getExt();
+        return ext;
     }
 
     public String getClassifier() {
-        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
+        return extraAttributes.get(Dependency.CLASSIFIER);
     }
     
     public File getFile() {
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
index 56a86d1..39ce7ad 100644
--- 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
@@ -17,6 +17,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 
 import java.io.File;
 
@@ -31,4 +32,10 @@ public interface ArtifactResolveResult {
      * @throws ArtifactResolveException If the resolution was unsuccessful.
      */
     File getFile() throws ArtifactResolveException;
+
+    /**
+     * @throws ArtifactResolveException If the resolution was unsuccessful.
+     */
+    @Nullable
+    ExternalResourceMetaData getExternalResourceMetaData() 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
index 30ae67f..cde8c47 100644
--- 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
@@ -21,5 +21,5 @@ public interface ArtifactResolver {
     /**
      * Resolves the given artifact. Any failures are packaged up in the result.
      */
-    ArtifactResolveResult resolve(Artifact artifact);
+    void resolve(Artifact artifact, BuildableArtifactResolveResult result);
 }
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
deleted file mode 100644
index 24c2bcc..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenArtifactResolveResult.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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
deleted file mode 100644
index 7b8288b..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenModuleVersionResolveResult.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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/BuildableArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.java
new file mode 100644
index 0000000..977f5a1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableArtifactResolveResult.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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+public interface BuildableArtifactResolveResult extends ArtifactResolveResult {
+    /**
+     * Marks the module version as resolved, with the given meta-data and artifact resolver.
+     */
+    void resolved(File file, @Nullable ExternalResourceMetaData resourceMetaData);
+
+    /**
+     * Marks the resolve as failed with the given exception.
+     */
+    void failed(ArtifactResolveException failure);
+
+    /**
+     * Marks the module version as not found.
+     */
+    void notFound(Artifact artifact);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
new file mode 100644
index 0000000..59d7805
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.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.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public interface BuildableModuleVersionResolveResult extends ModuleVersionResolveResult {
+    /**
+     * Marks the module version as resolved, with the given meta-data and artifact resolver.
+     */
+    void resolved(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
+
+    /**
+     * Marks the resolve as failed with the given exception.
+     */
+    void failed(ModuleVersionResolveException failure);
+
+    /**
+     * Marks the module version as not found.
+     */
+    void notFound(ModuleRevisionId moduleRevisionId);
+
+    /**
+     * Replaces the meta-data in the result. Result must already be resolved.
+     */
+    void setMetaData(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor);
+
+    /**
+     * Replaces the artifact resolver in the result. Result must already be resolved.
+     */
+    void setArtifactResolver(ArtifactResolver artifactResolver);
+}
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
index 9fc7aac..ddcc74b 100644
--- 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
@@ -16,10 +16,10 @@
 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.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.internal.Factory;
 
 public class CacheLockingArtifactDependencyResolver implements ArtifactDependencyResolver {
     private final CacheLockingManager lockingManager;
@@ -30,11 +30,11 @@ public class CacheLockingArtifactDependencyResolver implements ArtifactDependenc
         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() {
+    public ResolverResults resolve(final ConfigurationInternal configuration) throws ResolveException {
+        return lockingManager.useCache(String.format("resolve %s", configuration), new Factory<ResolverResults>() {
+            public ResolverResults create() {
                 return resolver.resolve(configuration);
             }
         });
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java
new file mode 100644
index 0000000..497bc8e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResult.java
@@ -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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactNotFoundException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+public class DefaultBuildableArtifactResolveResult implements BuildableArtifactResolveResult {
+    private ArtifactResolveException failure;
+    private File file;
+    private ExternalResourceMetaData externalResourceMetaData;
+
+    public void failed(ArtifactResolveException failure) {
+        this.failure = failure;
+    }
+
+    public void resolved(File file, @Nullable ExternalResourceMetaData externalResourceMetaData) {
+        this.file = file;
+        this.externalResourceMetaData = externalResourceMetaData;
+    }
+
+    public void notFound(Artifact artifact) {
+        failed(new ArtifactNotFoundException(artifact));
+    }
+
+    public ArtifactResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    public File getFile() throws ArtifactResolveException {
+        assertResolved();
+        return file;
+    }
+
+    public ExternalResourceMetaData getExternalResourceMetaData() throws ArtifactResolveException {
+        assertResolved();
+        return externalResourceMetaData;
+    }
+
+    private void assertResolved() {
+        assertHasResult();
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    private void assertHasResult() {
+        if (failure == null && file == null) {
+            throw new IllegalStateException("No result has been specified.");
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
new file mode 100644
index 0000000..681cec4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
@@ -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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class DefaultBuildableModuleVersionResolveResult implements BuildableModuleVersionResolveResult {
+    private ModuleRevisionId moduleRevisionId;
+    private ModuleDescriptor moduleDescriptor;
+    private ModuleVersionResolveException failure;
+    private ArtifactResolver artifactResolver;
+
+    public void failed(ModuleVersionResolveException failure) {
+        moduleDescriptor = null;
+        this.failure = failure;
+    }
+
+    public void notFound(ModuleRevisionId moduleRevisionId) {
+        failed(new ModuleVersionNotFoundException(moduleRevisionId));
+    }
+
+    public void resolved(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
+        this.moduleRevisionId = moduleRevisionId;
+        this.moduleDescriptor = descriptor;
+        this.artifactResolver = artifactResolver;
+    }
+
+    public void setMetaData(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor) {
+        assertResolved();
+        this.moduleRevisionId = moduleRevisionId;
+        this.moduleDescriptor = descriptor;
+    }
+
+    public void setArtifactResolver(ArtifactResolver artifactResolver) {
+        assertResolved();
+        this.artifactResolver = artifactResolver;
+    }
+
+    public ModuleRevisionId getId() throws ModuleVersionResolveException {
+        assertResolved();
+        return moduleRevisionId;
+    }
+
+    public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+        assertResolved();
+        return moduleDescriptor;
+    }
+
+    public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+        assertResolved();
+        return artifactResolver;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    private void assertResolved() {
+        assertHasResult();
+        if (failure != null) {
+            throw failure;
+        }
+    }
+
+    private void assertHasResult() {
+        if (failure == null && moduleDescriptor == null) {
+            throw new IllegalStateException("No result has been specified.");
+        }
+    }
+}
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
index 61f749c..ab6ce1c 100644
--- 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
@@ -25,7 +25,7 @@ import org.gradle.cache.internal.FileLockManager;
 import java.io.File;
 
 public class DefaultCacheLockingManager implements CacheLockingManager {
-    public static final int CACHE_LAYOUT_VERSION = 14;
+    public static final int CACHE_LAYOUT_VERSION = 15;
     private final PersistentCache cache;
 
     public DefaultCacheLockingManager(CacheRepository cacheRepository) {
diff --git a/subprojects/core-impl/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
index b189207..7a765ed 100644
--- a/subprojects/core-impl/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
@@ -120,9 +120,8 @@ public class DefaultIvyDependencyPublisher implements IvyDependencyPublisher {
                 if (isSigningArtifact(artifact)) {
                     return;
                 }
-                String message = String.format("Attempted to publish an artifact that does not exist. File '%s' for artifact %s does not exist.\n"
-                        + "Relying on this behaviour is deprecated: in a future release of Gradle this build will fail.", artifactFile, artifact.getModuleRevisionId());
-                DeprecationLogger.nagUserWith(message);
+                String message = String.format("Attempted to publish an artifact '%s' that does not exist '%s'", artifact.getModuleRevisionId(), artifactFile);
+                DeprecationLogger.nagUserOfDeprecatedBehaviour(message);
             }
         }
 
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
index 2702701..48d2233 100755
--- 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
@@ -18,7 +18,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.UnresolvedDependency;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.util.DeprecationLogger;
 
 public class DefaultUnresolvedDependency implements UnresolvedDependency {
@@ -28,7 +28,7 @@ public class DefaultUnresolvedDependency implements UnresolvedDependency {
 
     public DefaultUnresolvedDependency(ModuleRevisionId id, Throwable problem) {
         revisionId = id;
-        this.selector = new DefaultModuleVersionIdentifier(id.getOrganisation(), id.getName(), id.getRevision());
+        this.selector = new DefaultModuleVersionSelector(id.getOrganisation(), id.getName(), id.getRevision());
         this.problem = problem;
     }
 
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
index f95d9b9..fd13ebd 100755
--- 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
@@ -23,8 +23,6 @@ import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 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);
+    void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result);
 }
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
index 39da9b3..90cd90b 100644
--- 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
@@ -17,6 +17,7 @@ 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.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.specs.Spec;
 
@@ -30,14 +31,15 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         this.dependencyResolver = dependencyResolver;
     }
 
-    public ResolvedConfiguration resolve(final ConfigurationInternal configuration) {
-        final ResolvedConfiguration resolvedConfiguration;
+    public ResolverResults resolve(final ConfigurationInternal configuration) {
+        final ResolverResults results;
         try {
-            resolvedConfiguration = dependencyResolver.resolve(configuration);
+            results = dependencyResolver.resolve(configuration);
         } catch (final Throwable e) {
-            return new BrokenResolvedConfiguration(e, configuration);
+            return new ResolverResults(new BrokenResolvedConfiguration(e, configuration), wrapException(e, configuration));
         }
-        return new ErrorHandlingResolvedConfiguration(resolvedConfiguration, configuration);
+        ResolvedConfiguration withErrorHandling = new ErrorHandlingResolvedConfiguration(results.getResolvedConfiguration(), configuration);
+        return results.withResolvedConfiguration(withErrorHandling);
     }
 
     private static ResolveException wrapException(Throwable e, Configuration 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
index ae203e5..60b70a6 100644
--- 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
@@ -15,11 +15,19 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.Transformers;
+import org.gradle.api.internal.XmlTransformer;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 
 import java.io.File;
+import java.util.Set;
+import java.util.TreeSet;
+
+import static org.gradle.util.CollectionUtils.collect;
+import static org.gradle.util.CollectionUtils.join;
 
 public class ErrorHandlingArtifactPublisher implements ArtifactPublisher {
     private final ArtifactPublisher artifactPublisher;
@@ -28,11 +36,16 @@ public class ErrorHandlingArtifactPublisher implements ArtifactPublisher {
         this.artifactPublisher = artifactPublisher;
     }
 
-    public void publish(ConfigurationInternal configuration, File descriptorDestination) {
+    public void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, XmlTransformer descriptorModifier) {
         try {
-            artifactPublisher.publish(configuration, descriptorDestination);
+            artifactPublisher.publish(module, configurations, descriptorDestination, descriptorModifier);
         } catch (Throwable e) {
-            throw new PublishException(String.format("Could not publish %s.", configuration), e);
+            String message = String.format(
+                    "Could not publish configuration%s: [%s]",
+                    configurations.size() > 1 ? "s" : "",
+                    join(", ", collect(configurations, new TreeSet(), Transformers.name(new Configuration.Namer())))
+            );
+            throw new PublishException(message, 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
deleted file mode 100644
index fdc525c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/FileBackedArtifactResolveResult.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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/ForcedModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ForcedModuleVersionIdResolveResult.java
new file mode 100644
index 0000000..ce4b1f6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ForcedModuleVersionIdResolveResult.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.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+/**
+ * by Szczepan Faber, created at: 8/29/12
+ */
+class ForcedModuleVersionIdResolveResult implements ModuleVersionIdResolveResult {
+
+    final ModuleVersionIdResolveResult result;
+
+    public ForcedModuleVersionIdResolveResult(ModuleVersionIdResolveResult result) {
+        this.result = result;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        return result.getFailure();
+    }
+
+    public ModuleRevisionId getId() throws ModuleVersionResolveException {
+        return result.getId();
+    }
+
+    public ModuleVersionResolveResult resolve() {
+        return result.resolve();
+    }
+
+    public IdSelectionReason getSelectionReason() {
+        return IdSelectionReason.forced;
+    }
+}
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
index bdb7e20..3e1ee67 100644
--- 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
@@ -1,94 +1,88 @@
-/*
- * 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.getEventManager());
-    }
-
-    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);
-        }
-    }
-}
+/*
+ * 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.Nullable;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+
+import java.io.File;
+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;
+    private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
+
+    public IvyBackedArtifactPublisher(ResolverProvider resolverProvider,
+                                      SettingsConverter settingsConverter,
+                                      ModuleDescriptorConverter publishModuleDescriptorConverter,
+                                      ModuleDescriptorConverter fileModuleDescriptorConverter,
+                                      IvyFactory ivyFactory,
+                                      IvyDependencyPublisher dependencyPublisher,
+                                      IvyModuleDescriptorWriter ivyModuleDescriptorWriter) {
+        this.resolverProvider = resolverProvider;
+        this.settingsConverter = settingsConverter;
+        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
+        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
+        this.ivyFactory = ivyFactory;
+        this.dependencyPublisher = dependencyPublisher;
+        this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
+    }
+
+    private Ivy ivyForPublish(List<DependencyResolver> publishResolvers) {
+        return ivyFactory.createIvy(settingsConverter.convertForPublish(publishResolvers));
+    }
+
+    public void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, @Nullable XmlTransformer descriptorModifier) throws PublishException {
+        List<DependencyResolver> publishResolvers = resolverProvider.getResolvers();
+        Ivy ivy = ivyForPublish(publishResolvers);
+        Set<String> confs = Configurations.getNames(configurations, false);
+        writeDescriptorFile(descriptorDestination, configurations, module, descriptorModifier);
+        dependencyPublisher.publish(
+                confs,
+                publishResolvers,
+                publishModuleDescriptorConverter.convert(configurations, module),
+                descriptorDestination,
+                ivy.getEventManager());
+    }
+
+    private void writeDescriptorFile(File descriptorDestination, Set<? extends Configuration> configurationsToPublish, Module module, XmlTransformer descriptorModifier) {
+        if (descriptorDestination == null) {
+            return;
+        }
+        assert configurationsToPublish.size() > 0;
+        Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
+        ModuleDescriptor moduleDescriptor = fileModuleDescriptorConverter.convert(allConfigurations, module);
+        ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorDestination, descriptorModifier);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
new file mode 100644
index 0000000..ab6681a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.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.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.internal.XmlTransformer;
+
+import java.io.File;
+
+public interface IvyModuleDescriptorWriter {
+
+    public void write(ModuleDescriptor md, File output);
+
+    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorModifier);
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
new file mode 100644
index 0000000..ace5c83
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.matcher.MapMatcher;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.StringUtils;
+import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.ExtendableItem;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.XmlTransformer;
+import org.gradle.util.TextUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+
+public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
+
+    private static Action<Writer> createWriterAction(final ModuleDescriptor md) {
+        return new ErroringAction<Writer>() {
+            protected void doExecute(Writer writer) throws IOException {
+                writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+                StringBuffer xmlNamespace = new StringBuffer();
+                Map namespaces = md.getExtraAttributesNamespaces();
+                for (Iterator iter = namespaces.entrySet().iterator(); iter.hasNext();) {
+                    Map.Entry ns = (Map.Entry) iter.next();
+                    xmlNamespace.append(" xmlns:").append(ns.getKey()).append("=\"")
+                            .append(ns.getValue()).append("\"");
+                }
+
+                String version = "2.0";
+                if (md.getInheritedDescriptors().length > 0) {
+                    version = "2.2";
+                }
+
+                writer.write("<ivy-module version=\"" + version + "\"" + xmlNamespace + ">");
+                writer.write(TextUtil.getPlatformLineSeparator());
+                printInfoTag(md, writer);
+                printConfigurations(md, writer);
+                printPublications(md, writer);
+                printDependencies(md, writer);
+                writer.write("</ivy-module>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        };
+    }
+
+    public void write(ModuleDescriptor md, File output) {
+        write(md, output, null);
+    }
+
+    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorTransformer) {
+        if (descriptorTransformer == null) {
+            descriptorTransformer = new XmlTransformer();
+        }
+
+        descriptorTransformer.transform(output, "UTF-8", createWriterAction(md));
+    }
+
+    private static void printDependencies(ModuleDescriptor md, Writer writer) throws IOException {
+        DependencyDescriptor[] dds = md.getDependencies();
+        if (dds.length > 0) {
+            writer.write("\t<dependencies>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+            for (int i = 0; i < dds.length; i++) {
+                DependencyDescriptor dep = dds[i];
+                writer.write("\t\t");
+                printDependency(md, dep, writer);
+            }
+            printAllExcludes(md, writer);
+            printAllMediators(md, writer);
+            writer.write("\t</dependencies>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+    }
+
+    protected static void printDependency(ModuleDescriptor md, DependencyDescriptor dep,
+                                          Writer writer) throws IOException {
+        writer.write("<dependency");
+        ModuleRevisionId dependencyRevisionId = dep.getDependencyRevisionId();
+        writer.write(" org=\""
+                + XMLHelper.escape(dependencyRevisionId.getOrganisation()) + "\"");
+        writer.write(" name=\""
+                + XMLHelper.escape(dependencyRevisionId.getName()) + "\"");
+        if (dependencyRevisionId.getBranch() != null) {
+            writer.write(" branch=\""
+                    + XMLHelper.escape(dependencyRevisionId.getBranch()) + "\"");
+        }
+        writer.write(" rev=\""
+                + XMLHelper.escape(dependencyRevisionId.getRevision()) + "\"");
+        if (!dep.getDynamicConstraintDependencyRevisionId()
+                .equals(dependencyRevisionId)) {
+            if (dep.getDynamicConstraintDependencyRevisionId().getBranch() != null) {
+                writer.write(" branchConstraint=\"" + XMLHelper.escape(
+                        dep.getDynamicConstraintDependencyRevisionId().getBranch()) + "\"");
+            }
+            writer.write(" revConstraint=\"" + XMLHelper.escape(
+                    dep.getDynamicConstraintDependencyRevisionId().getRevision()) + "\"");
+        }
+        if (dep.isForce()) {
+            writer.write(" force=\"" + dep.isForce() + "\"");
+        }
+        if (dep.isChanging()) {
+            writer.write(" changing=\"" + dep.isChanging() + "\"");
+        }
+        if (!dep.isTransitive()) {
+            writer.write(" transitive=\"" + dep.isTransitive() + "\"");
+        }
+        writer.write(" conf=\"");
+        String[] modConfs = dep.getModuleConfigurations();
+        for (int j = 0; j < modConfs.length; j++) {
+            String[] depConfs = dep.getDependencyConfigurations(modConfs[j]);
+            writer.write(XMLHelper.escape(modConfs[j]) + "->");
+            for (int k = 0; k < depConfs.length; k++) {
+                writer.write(XMLHelper.escape(depConfs[k]));
+                if (k + 1 < depConfs.length) {
+                    writer.write(",");
+                }
+            }
+            if (j + 1 < modConfs.length) {
+                writer.write(";");
+            }
+        }
+        writer.write("\"");
+
+        printExtraAttributes(dep, writer, " ");
+
+        DependencyArtifactDescriptor[] depArtifacts = dep.getAllDependencyArtifacts();
+        if (depArtifacts.length > 0) {
+            writer.write(">");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        printDependencyArtefacts(md, writer, depArtifacts);
+
+        IncludeRule[] includes = dep.getAllIncludeRules();
+        if (includes.length > 0 && depArtifacts.length == 0) {
+            writer.write(">");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        printDependencyIncludeRules(md, writer, includes);
+
+        ExcludeRule[] excludes = dep.getAllExcludeRules();
+        if (excludes.length > 0 && includes.length == 0 && depArtifacts.length == 0) {
+            writer.write(">");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        printDependencyExcludeRules(md, writer, excludes);
+        if (includes.length + excludes.length + depArtifacts.length == 0) {
+            writer.write("/>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        } else {
+            writer.write("\t\t</dependency>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+    }
+
+    private static void printAllMediators(ModuleDescriptor md, Writer writer) throws IOException {
+        Map/*<MapMatcher, DependencyDescriptorMediator>*/ mediators
+                = md.getAllDependencyDescriptorMediators().getAllRules();
+
+        for (Iterator iterator = mediators.entrySet().iterator(); iterator.hasNext();) {
+            Map.Entry mediatorRule = (Map.Entry) iterator.next();
+            MapMatcher matcher = (MapMatcher) mediatorRule.getKey();
+            DependencyDescriptorMediator mediator =
+                    (DependencyDescriptorMediator) mediatorRule.getValue();
+
+            if (mediator instanceof OverrideDependencyDescriptorMediator) {
+                OverrideDependencyDescriptorMediator oddm =
+                        (OverrideDependencyDescriptorMediator) mediator;
+
+                writer.write("\t\t<override");
+                writer.write(" org=\"" + XMLHelper.escape(
+                        (String) matcher.getAttributes().get(IvyPatternHelper.ORGANISATION_KEY))
+                        + "\"");
+                writer.write(" module=\"" + XMLHelper.escape(
+                        (String) matcher.getAttributes().get(IvyPatternHelper.MODULE_KEY))
+                        + "\"");
+                writer.write(" matcher=\"" + XMLHelper.escape(
+                        matcher.getPatternMatcher().getName())
+                        + "\"");
+                if (oddm.getBranch() != null) {
+                    writer.write(" branch=\"" + XMLHelper.escape(oddm.getBranch()) + "\"");
+                }
+                if (oddm.getVersion() != null) {
+                    writer.write(" rev=\"" + XMLHelper.escape(oddm.getVersion()) + "\"");
+                }
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            } else {
+                Message.verbose("ignoring unhandled DependencyDescriptorMediator: "
+                        + mediator.getClass());
+            }
+        }
+    }
+
+    private static void printAllExcludes(ModuleDescriptor md, Writer writer) throws IOException {
+        ExcludeRule[] excludes = md.getAllExcludeRules();
+        if (excludes.length > 0) {
+            for (int j = 0; j < excludes.length; j++) {
+                writer.write("\t\t<exclude");
+                writer.write(" org=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getOrganisation())
+                        + "\"");
+                writer.write(" module=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getName())
+                        + "\"");
+                writer.write(" artifact=\"" + XMLHelper.escape(excludes[j].getId().getName()) + "\"");
+                writer.write(" type=\"" + XMLHelper.escape(excludes[j].getId().getType()) + "\"");
+                writer.write(" ext=\"" + XMLHelper.escape(excludes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = excludes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(
+                        Arrays.asList(md.getConfigurationsNames()))) {
+                    writer.write(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        writer.write(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            writer.write(",");
+                        }
+                    }
+                    writer.write("\"");
+                }
+                writer.write(" matcher=\""
+                        + XMLHelper.escape(excludes[j].getMatcher().getName()) + "\"");
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        }
+    }
+
+    private static void printDependencyExcludeRules(ModuleDescriptor md, Writer writer,
+                                                    ExcludeRule[] excludes) throws IOException {
+        if (excludes.length > 0) {
+            for (int j = 0; j < excludes.length; j++) {
+                writer.write("\t\t\t<exclude");
+                writer.write(" org=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getOrganisation())
+                        + "\"");
+                writer.write(" module=\""
+                        + XMLHelper.escape(excludes[j].getId().getModuleId().getName())
+                        + "\"");
+                writer.write(" name=\"" + XMLHelper.escape(excludes[j].getId().getName()) + "\"");
+                writer.write(" type=\"" + XMLHelper.escape(excludes[j].getId().getType()) + "\"");
+                writer.write(" ext=\"" + XMLHelper.escape(excludes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = excludes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(
+                        Arrays.asList(md.getConfigurationsNames()))) {
+                    writer.write(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        writer.write(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            writer.write(",");
+                        }
+                    }
+                    writer.write("\"");
+                }
+                writer.write(" matcher=\""
+                        + XMLHelper.escape(excludes[j].getMatcher().getName()) + "\"");
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        }
+    }
+
+    private static void printDependencyIncludeRules(ModuleDescriptor md, Writer writer,
+                                                    IncludeRule[] includes) throws IOException {
+        if (includes.length > 0) {
+            for (int j = 0; j < includes.length; j++) {
+                writer.write("\t\t\t<include");
+                writer.write(" name=\"" + XMLHelper.escape(includes[j].getId().getName()) + "\"");
+                writer.write(" type=\"" + XMLHelper.escape(includes[j].getId().getType()) + "\"");
+                writer.write(" ext=\"" + XMLHelper.escape(includes[j].getId().getExt()) + "\"");
+                String[] ruleConfs = includes[j].getConfigurations();
+                if (!Arrays.asList(ruleConfs).equals(
+                        Arrays.asList(md.getConfigurationsNames()))) {
+                    writer.write(" conf=\"");
+                    for (int k = 0; k < ruleConfs.length; k++) {
+                        writer.write(XMLHelper.escape(ruleConfs[k]));
+                        if (k + 1 < ruleConfs.length) {
+                            writer.write(",");
+                        }
+                    }
+                    writer.write("\"");
+                }
+                writer.write(" matcher=\""
+                        + XMLHelper.escape(includes[j].getMatcher().getName()) + "\"");
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        }
+    }
+
+    private static void printDependencyArtefacts(ModuleDescriptor md, Writer writer,
+                                                 DependencyArtifactDescriptor[] depArtifacts) throws IOException {
+        if (depArtifacts.length > 0) {
+            for (int j = 0; j < depArtifacts.length; j++) {
+                writer.write("\t\t\t<artifact");
+                writer.write(" name=\"" + XMLHelper.escape(depArtifacts[j].getName()) + "\"");
+                writer.write(" type=\"" + XMLHelper.escape(depArtifacts[j].getType()) + "\"");
+                writer.write(" ext=\"" + XMLHelper.escape(depArtifacts[j].getExt()) + "\"");
+                String[] dadconfs = depArtifacts[j].getConfigurations();
+                if (!Arrays.asList(dadconfs).equals(
+                        Arrays.asList(md.getConfigurationsNames()))) {
+                    writer.write(" conf=\"");
+                    for (int k = 0; k < dadconfs.length; k++) {
+                        writer.write(XMLHelper.escape(dadconfs[k]));
+                        if (k + 1 < dadconfs.length) {
+                            writer.write(",");
+                        }
+                    }
+                    writer.write("\"");
+                }
+                printExtraAttributes(depArtifacts[j], writer, " ");
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        }
+    }
+
+    /**
+     * Writes the extra attributes of the given {@link org.apache.ivy.util.extendable.ExtendableItem} to the given <tt>PrintWriter</tt>.
+     *
+     * @param item the {@link org.apache.ivy.util.extendable.ExtendableItem}, cannot be <tt>null</tt>
+     * @param writer the writer to use
+     * @param prefix the string to write before writing the attributes (if any)
+     */
+    private static void printExtraAttributes(ExtendableItem item, Writer writer, String prefix) throws IOException {
+        printExtraAttributes(item.getQualifiedExtraAttributes(), writer, prefix);
+    }
+
+    /**
+     * Writes the specified <tt>Map</tt> containing the extra attributes to the given <tt>PrintWriter</tt>.
+     *
+     * @param extra the extra attributes, can be <tt>null</tt>
+     * @param writer the writer to use
+     * @param prefix the string to write before writing the attributes (if any)
+     */
+    private static void printExtraAttributes(Map extra, Writer writer, String prefix) throws IOException {
+        if (extra == null) {
+            return;
+        }
+
+        String delim = prefix;
+        for (Iterator iter = extra.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            writer.write(delim + entry.getKey() + "=\""
+                    + XMLHelper.escape(entry.getValue().toString()) + "\"");
+            delim = " ";
+        }
+    }
+
+    private static void printPublications(ModuleDescriptor md, Writer writer) throws IOException {
+        writer.write("\t<publications>");
+        writer.write(TextUtil.getPlatformLineSeparator());
+        Artifact[] artifacts = md.getAllArtifacts();
+        for (int i = 0; i < artifacts.length; i++) {
+            writer.write("\t\t<artifact");
+            writer.write(" name=\"" + XMLHelper.escape(artifacts[i].getName()) + "\"");
+            writer.write(" type=\"" + XMLHelper.escape(artifacts[i].getType()) + "\"");
+            writer.write(" ext=\"" + XMLHelper.escape(artifacts[i].getExt()) + "\"");
+            writer.write(" conf=\"" + XMLHelper.escape(getConfs(md, artifacts[i])) + "\"");
+            printExtraAttributes(artifacts[i], writer, " ");
+            writer.write("/>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        writer.write("\t</publications>");
+        writer.write(TextUtil.getPlatformLineSeparator());
+    }
+
+    private static void printConfigurations(ModuleDescriptor md, Writer writer) throws IOException {
+        Configuration[] confs = md.getConfigurations();
+        if (confs.length > 0) {
+            writer.write("\t<configurations>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+            for (int i = 0; i < confs.length; i++) {
+                Configuration conf = confs[i];
+                writer.write("\t\t");
+                printConfiguration(conf, writer);
+            }
+            writer.write("\t</configurations>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+    }
+
+    protected static void printConfiguration(Configuration conf, Writer writer) throws IOException {
+        writer.write("<conf");
+        writer.write(" name=\"" + XMLHelper.escape(conf.getName()) + "\"");
+        writer.write(" visibility=\""
+                + XMLHelper.escape(conf.getVisibility().toString()) + "\"");
+        String description = conf.getDescription();
+        if (description != null) {
+            writer.write(" description=\""
+                    + XMLHelper.escape(description) + "\"");
+        }
+        String[] exts = conf.getExtends();
+        if (exts.length > 0) {
+            writer.write(" extends=\"");
+            for (int j = 0; j < exts.length; j++) {
+                writer.write(XMLHelper.escape(exts[j]));
+                if (j + 1 < exts.length) {
+                    writer.write(",");
+                }
+            }
+            writer.write("\"");
+        }
+        if (!conf.isTransitive()) {
+            writer.write(" transitive=\"false\"");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        if (conf.getDeprecated() != null) {
+            writer.write(" deprecated=\"" + XMLHelper.escape(conf.getDeprecated()) + "\"");
+        }
+        printExtraAttributes(conf, writer, " ");
+        writer.write("/>");
+        writer.write(TextUtil.getPlatformLineSeparator());
+    }
+
+    private static void printInfoTag(ModuleDescriptor md, Writer writer) throws IOException {
+        ModuleRevisionId moduleRevisionId = md.getModuleRevisionId();
+        writer.write("\t<info organisation=\""
+                + XMLHelper.escape(moduleRevisionId.getOrganisation())
+                + "\"");
+        writer.write(TextUtil.getPlatformLineSeparator());
+        writer.write("\t\tmodule=\"" + XMLHelper.escape(moduleRevisionId.getName()) + "\"");
+        writer.write(TextUtil.getPlatformLineSeparator());
+
+        ModuleRevisionId resolvedModuleRevisionId = md.getResolvedModuleRevisionId();
+        String branch = resolvedModuleRevisionId.getBranch();
+        if (branch != null) {
+            writer.write("\t\tbranch=\"" + XMLHelper.escape(branch) + "\"");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        String revision = resolvedModuleRevisionId.getRevision();
+        if (revision != null) {
+            writer.write("\t\trevision=\"" + XMLHelper.escape(revision) + "\"");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        writer.write("\t\tstatus=\"" + XMLHelper.escape(md.getStatus()) + "\"");
+        writer.write(TextUtil.getPlatformLineSeparator());
+        writer.write("\t\tpublication=\""
+                + Ivy.DATE_FORMAT.format(md.getResolvedPublicationDate()) + "\"");
+        writer.write(TextUtil.getPlatformLineSeparator());
+        if (md.isDefault()) {
+            writer.write("\t\tdefault=\"true\"");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        if (md instanceof DefaultModuleDescriptor) {
+            DefaultModuleDescriptor dmd = (DefaultModuleDescriptor) md;
+            if (dmd.getNamespace() != null && !dmd.getNamespace().getName().equals("system")) {
+                writer.write("\t\tnamespace=\""
+                        + XMLHelper.escape(dmd.getNamespace().getName()) + "\"");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+        }
+        if (!md.getExtraAttributes().isEmpty()) {
+            printExtraAttributes(md, writer, "\t\t");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+        if (requireInnerInfoElement(md)) {
+            writer.write("\t>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+            ExtendsDescriptor[] parents = md.getInheritedDescriptors();
+            for (int i = 0; i < parents.length; i++) {
+                ExtendsDescriptor parent = parents[i];
+                ModuleRevisionId mrid = parent.getParentRevisionId();
+                writer.write("\t\t<extends organisation=\"" + XMLHelper.escape(mrid.getOrganisation()) + "\""
+                        + " module=\"" + XMLHelper.escape(mrid.getName()) + "\""
+                        + " revision=\"" + XMLHelper.escape(mrid.getRevision()) + "\"");
+
+                String location = parent.getLocation();
+                if (location != null) {
+                    writer.write(" location=\"" + XMLHelper.escape(location) + "\"");
+                }
+                writer.write(" extendType=\"" + StringUtils.join(parent.getExtendsTypes(), ",") + "\"");
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+            License[] licenses = md.getLicenses();
+            for (int i = 0; i < licenses.length; i++) {
+                License license = licenses[i];
+                writer.write("\t\t<license ");
+                if (license.getName() != null) {
+                    writer.write("name=\"" + XMLHelper.escape(license.getName()) + "\" ");
+                }
+                if (license.getUrl() != null) {
+                    writer.write("url=\"" + XMLHelper.escape(license.getUrl()) + "\" ");
+                }
+                writer.write("/>");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+            if (md.getHomePage() != null || md.getDescription() != null) {
+                writer.write("\t\t<description");
+                if (md.getHomePage() != null) {
+                    writer.write(" homepage=\"" + XMLHelper.escape(md.getHomePage()) + "\"");
+                }
+                if (md.getDescription() != null && md.getDescription().trim().length() > 0) {
+                    writer.write(">");
+                    writer.write(TextUtil.getPlatformLineSeparator());
+                    writer.write("\t\t" + XMLHelper.escape(md.getDescription()));
+                    writer.write(TextUtil.getPlatformLineSeparator());
+                    writer.write("\t\t</description>");
+                    writer.write(TextUtil.getPlatformLineSeparator());
+                } else {
+                    writer.write(" />");
+                    writer.write(TextUtil.getPlatformLineSeparator());
+                }
+            }
+            for (Iterator it = md.getExtraInfo().entrySet().iterator(); it.hasNext();) {
+                Map.Entry extraDescr = (Map.Entry) it.next();
+                if (extraDescr.getValue() == null
+                        || ((String) extraDescr.getValue()).length() == 0) {
+                    continue;
+                }
+                writer.write("\t\t<");
+                writer.write(extraDescr.getKey().toString());
+                writer.write(">");
+                writer.write(XMLHelper.escape((String) extraDescr.getValue()));
+                writer.write("</");
+                writer.write(extraDescr.getKey().toString());
+                writer.write(">");
+                writer.write(TextUtil.getPlatformLineSeparator());
+            }
+            writer.write("\t</info>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        } else {
+            writer.write("\t/>");
+            writer.write(TextUtil.getPlatformLineSeparator());
+        }
+    }
+
+    private static boolean requireInnerInfoElement(ModuleDescriptor md) {
+        return md.getExtraInfo().size() > 0
+                || md.getHomePage() != null
+                || (md.getDescription() != null && md.getDescription().trim().length() > 0)
+                || md.getLicenses().length > 0
+                || md.getInheritedDescriptors().length > 0;
+    }
+
+    private static String getConfs(ModuleDescriptor md, Artifact artifact) {
+        StringBuffer ret = new StringBuffer();
+
+        String[] confs = md.getConfigurationsNames();
+        for (int i = 0; i < confs.length; i++) {
+            if (Arrays.asList(md.getArtifacts(confs[i])).contains(artifact)) {
+                ret.append(confs[i]).append(",");
+            }
+        }
+        if (ret.length() > 0) {
+            ret.setLength(ret.length() - 1);
+        }
+        return ret.toString();
+    }
+}
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
index 374166d..ba8e2a1 100644
--- 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
@@ -36,4 +36,14 @@ public interface ModuleVersionIdResolveResult {
      * Resolves the meta-data for this module version, if required. Failures are packaged up in the result.
      */
     ModuleVersionResolveResult resolve();
+
+    /**
+     * @return why given id was selected
+     */
+    IdSelectionReason getSelectionReason();
+
+    public static enum IdSelectionReason {
+        requested, forced
+        //TODO SF consider changing to an interface with isForced()
+    }
 }
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
index d0ec480..5b5b951 100644
--- 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
@@ -35,7 +35,9 @@ public class ResolvedArtifactFactory {
             public File create() {
                 return lockingManager.useCache(String.format("download %s", artifact), new Factory<File>() {
                     public File create() {
-                        return resolver.resolve(artifact).getFile();
+                        DefaultBuildableArtifactResolveResult result = new DefaultBuildableArtifactResolveResult();
+                        resolver.resolve(artifact, result);
+                        return result.getFile();
                     }
                 });
             }
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
index f0e57ea..408b5d4 100644
--- 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
@@ -17,8 +17,9 @@ 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.CachingDependencyResolveContext;
+import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.specs.Spec;
 import org.gradle.util.CollectionUtils;
@@ -34,52 +35,73 @@ public class SelfResolvingDependencyResolver implements ArtifactDependencyResolv
         this.resolver = resolver;
     }
 
-    public ArtifactDependencyResolver getResolver() {
-        return resolver;
+    public ResolverResults resolve(final ConfigurationInternal configuration) {
+        ResolverResults results = resolver.resolve(configuration);
+        ResolvedConfiguration resolvedConfiguration = results.getResolvedConfiguration();
+        Set<Dependency> dependencies = configuration.getAllDependencies();
+        CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
+        SelfResolvingFilesProvider provider = new SelfResolvingFilesProvider(resolveContext, dependencies);
+
+        return results.withResolvedConfiguration(new FilesAggregatingResolvedConfiguration(resolvedConfiguration, provider));
     }
 
-    public ResolvedConfiguration resolve(final ConfigurationInternal configuration) {
-        final ResolvedConfiguration resolvedConfiguration = resolver.resolve(configuration);
-        final Set<Dependency> dependencies = configuration.getAllDependencies();
+    protected static class SelfResolvingFilesProvider {
 
-        return new ResolvedConfiguration() {
-            private final CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
+        final CachingDependencyResolveContext resolveContext;
+        final Set<Dependency> dependencies;
 
-            public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
-                Set<File> files = new LinkedHashSet<File>();
+        public SelfResolvingFilesProvider(CachingDependencyResolveContext resolveContext, Set<Dependency> dependencies) {
+            this.resolveContext = resolveContext;
+            this.dependencies = dependencies;
+        }
 
-                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;
+        Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+            Set<Dependency> selectedDependencies = CollectionUtils.filter(dependencies, dependencySpec);
+            for (Dependency dependency : selectedDependencies) {
+                resolveContext.add(dependency);
             }
+            return resolveContext.resolve().getFiles();
+        }
+    }
 
-            public Set<ResolvedArtifact> getResolvedArtifacts() {
-                return resolvedConfiguration.getResolvedArtifacts();
-            }
+    protected static class FilesAggregatingResolvedConfiguration implements ResolvedConfiguration {
+        final ResolvedConfiguration resolvedConfiguration;
+        final SelfResolvingFilesProvider selfResolvingFilesProvider;
 
-            public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-                return resolvedConfiguration.getFirstLevelModuleDependencies();
-            }
+        FilesAggregatingResolvedConfiguration(ResolvedConfiguration resolvedConfiguration, SelfResolvingFilesProvider selfResolvingFilesProvider) {
+            this.resolvedConfiguration = resolvedConfiguration;
+            this.selfResolvingFilesProvider = selfResolvingFilesProvider;
+        }
 
-            public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
-                return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
-            }
+        public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+            Set<File> files = new LinkedHashSet<File>();
+            files.addAll(selfResolvingFilesProvider.getFiles(dependencySpec));
+            files.addAll(resolvedConfiguration.getFiles(dependencySpec));
+            return files;
+        }
 
-            public boolean hasError() {
-                return resolvedConfiguration.hasError();
-            }
+        public Set<ResolvedArtifact> getResolvedArtifacts() {
+            return resolvedConfiguration.getResolvedArtifacts();
+        }
 
-            public LenientConfiguration getLenientConfiguration() {
-                return resolvedConfiguration.getLenientConfiguration();
-            }
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+            return resolvedConfiguration.getFirstLevelModuleDependencies();
+        }
 
-            public void rethrowFailure() throws GradleException {
-                resolvedConfiguration.rethrowFailure();
-            }
-        };
+        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/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
index 13887c2..97fbc1d 100644
--- 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
@@ -17,7 +17,11 @@ 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.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
 import org.gradle.api.specs.Spec;
 
 import java.io.File;
@@ -25,7 +29,23 @@ import java.util.Collections;
 import java.util.Set;
 
 public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements ArtifactDependencyResolver {
-    private final ResolvedConfiguration emptyConfig = new ResolvedConfiguration() {
+    private final ArtifactDependencyResolver dependencyResolver;
+
+    public ShortcircuitEmptyConfigsArtifactDependencyResolver(ArtifactDependencyResolver dependencyResolver) {
+        this.dependencyResolver = dependencyResolver;
+    }
+
+    public ResolverResults resolve(ConfigurationInternal configuration) {
+        if (configuration.getAllDependencies().isEmpty()) {
+            ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(configuration.getModule());
+            DefaultResolutionResult emptyResult = new ResolutionResultBuilder().start(id).getResult();
+            return new ResolverResults(new EmptyResolvedConfiguration(), emptyResult);
+        }
+        return dependencyResolver.resolve(configuration);
+    }
+
+    private static class EmptyResolvedConfiguration implements ResolvedConfiguration {
+
         public boolean hasError() {
             return false;
         }
@@ -68,17 +88,5 @@ public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements Artif
         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
index e21c923..9765061 100644
--- 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
@@ -38,7 +38,8 @@ public class VersionForcingDependencyToModuleResolver implements DependencyToMod
     public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
         ModuleRevisionId newRevisionId = forcedModules.get(dependencyDescriptor.getDependencyId());
         if (newRevisionId != null) {
-            return resolver.resolve(dependencyDescriptor.clone(newRevisionId));
+            ModuleVersionIdResolveResult result = resolver.resolve(dependencyDescriptor.clone(newRevisionId));
+            return new ForcedModuleVersionIdResolveResult(result);
         }
         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
index 4aaebef..cd7c769 100644
--- 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
@@ -18,11 +18,8 @@ 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.BuildableModuleVersionResolveResult;
 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;
 
 /**
@@ -35,42 +32,16 @@ public class ClientModuleResolver implements DependencyToModuleResolver {
         this.resolver = resolver;
     }
 
-    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
-        final ModuleVersionResolveResult resolveResult = resolver.resolve(dependencyDescriptor);
+    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
+        resolver.resolve(dependencyDescriptor, result);
 
-        if (resolveResult.getFailure() != null || !(dependencyDescriptor instanceof ClientModuleDependencyDescriptor)) {
-            return resolveResult;
+        if (result.getFailure() != null || !(dependencyDescriptor instanceof ClientModuleDependencyDescriptor)) {
+            return;
         }
 
         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();
-        }
+        result.setMetaData(moduleDescriptor.getModuleRevisionId(), moduleDescriptor);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
new file mode 100644
index 0000000..553dad2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.DependencyDescriptor;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+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.cachemanager.LocalFileRepositoryCacheManager;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.ParseException;
+
+public abstract class AbstractDependencyResolverAdapter implements ModuleVersionRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDependencyResolverAdapter.class);
+    private final DependencyResolverIdentifier identifier;
+    protected final DependencyResolver resolver;
+
+    public AbstractDependencyResolverAdapter(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;
+    }
+
+    protected 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 void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
+        try {
+            ResolvedModuleRevision revision = resolver.getDependency(dependencyDescriptor, resolveData);
+            if (revision == null) {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                result.missing();
+            } else {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                result.resolved(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/BuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
new file mode 100644
index 0000000..4e6811a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.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.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+/**
+ * The result of attempting to resolve a dependency descriptor to the meta-data for a module version.
+ */
+public interface BuildableModuleVersionDescriptor extends ModuleVersionDescriptor {
+    enum State {
+        Resolved, Missing, Failed, ProbablyMissing, Unknown
+    }
+
+    /**
+     * Returns the current state of this descriptor.
+     */
+    State getState();
+
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Marks the module version as resolved, with the given meta-data.
+     */
+    void resolved(ModuleDescriptor descriptor, boolean changing);
+
+    /**
+     * Marks the resolve as failed with the given exception.
+     */
+    void failed(ModuleVersionResolveException failure);
+
+    /**
+     * Marks the module version as definitely missing.
+     */
+    void missing();
+
+    /**
+     * Marks the module version as probably missing.
+     */
+    void probablyMissing();
+
+}
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
index 16d45c4..cac2014 100644
--- 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
@@ -17,9 +17,8 @@ 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.BuildableArtifactResolveResult;
 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.
@@ -45,18 +44,18 @@ public class CacheLockingModuleVersionRepository implements ModuleVersionReposit
         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 void getDependency(final DependencyDescriptor dependencyDescriptor, final BuildableModuleVersionDescriptor result) {
+        cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dependencyDescriptor, getId()), new Runnable() {
+            public void run() {
+                repository.getDependency(dependencyDescriptor, result);
             }
         });
     }
 
-    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);
+    public void resolve(final Artifact artifact, final BuildableArtifactResolveResult result) {
+        cacheLockingManager.longRunningOperation(String.format("Download %s using repository %s", artifact, getId()), new Runnable() {
+            public void run() {
+                repository.resolve(artifact, result);
             }
         });
     }
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
index 0922644..dfe38e0 100644
--- 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
@@ -24,7 +24,9 @@ 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.DefaultModuleVersionSelector;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 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;
@@ -37,13 +39,13 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 
-public class CachingModuleVersionRepository implements ModuleVersionRepository {
+public class CachingModuleVersionRepository implements LocalAwareModuleVersionRepository {
     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;
@@ -77,21 +79,26 @@ public class CachingModuleVersionRepository implements ModuleVersionRepository {
         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;
+    public void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        DependencyDescriptor resolvedDependencyDescriptor = maybeUseCachedDynamicVersion(delegate, dependencyDescriptor);
+        lookupModuleInCache(delegate, resolvedDependencyDescriptor, result);
+    }
+
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        delegate.getDependency(ForceChangeDependencyDescriptor.forceChangingFlag(dependencyDescriptor, true), result);
+        switch (result.getState()) {
+            case Missing:
+                moduleDescriptorCache.cacheModuleDescriptor(delegate, dependencyDescriptor.getDependencyRevisionId(), null, dependencyDescriptor.isChanging());
+                break;
+            case Resolved:
+                moduleResolutionCache.cacheModuleResolution(delegate, dependencyDescriptor.getDependencyRevisionId(), result.getId());
+                moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), isChangingDependency(dependencyDescriptor, result));
+                break;
+            case Failed:
+                break;
+            default:
+                throw new IllegalStateException("Unexpected resolve state: " + result.getState());
         }
-        return resolveModule(resolvedDependencyDescriptor, requestedDependencyDescriptor);
     }
 
     private DependencyDescriptor maybeUseCachedDynamicVersion(ModuleVersionRepository repository, DependencyDescriptor original) {
@@ -105,46 +112,52 @@ public class CachingModuleVersionRepository implements ModuleVersionRepository {
                 return original;
             } else {
                 LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'",
-                        new Object[] {repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId});
+                        new Object[]{repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId});
                 return original.clone(cachedModuleResolution.getResolvedVersion());
             }
         }
         return original;
     }
 
-    public CachedModuleLookup lookupModuleInCache(ModuleVersionRepository repository, DependencyDescriptor resolvedDependencyDescriptor) {
+    public void lookupModuleInCache(ModuleVersionRepository repository, DependencyDescriptor resolvedDependencyDescriptor, BuildableModuleVersionDescriptor result) {
         ModuleRevisionId resolvedModuleVersionId = resolvedDependencyDescriptor.getDependencyRevisionId();
         ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(resolvedModuleVersionId);
         ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, resolvedModuleVersionId);
         if (cachedModuleDescriptor == null) {
-            return notFound();
+            return;
         }
         if (cachedModuleDescriptor.isMissing()) {
-            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, null, cachedModuleDescriptor.getAgeMillis())) {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, null, resolvedModuleVersionId, cachedModuleDescriptor.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for missing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
-                return notFound();
+                return;
             }
             LOGGER.debug("Detected non-existence of module '{}' in resolver cache '{}'", resolvedModuleVersionId, repository.getName());
-            return found(null);
+            if (cachedModuleDescriptor.getAgeMillis() == 0) {
+                // Verified since the start of this build, assume still missing
+                result.missing();
+            } else {
+                // Was missing last time we checked
+                result.probablyMissing();
+            }
+            return;
         }
         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();
+                return;
             }
             LOGGER.debug("Found cached version of changing module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
         } else {
-            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), null, cachedModuleDescriptor.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for module must be refreshed: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
-                return notFound();
+                return;
             }
         }
 
         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);
+        result.resolved(cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule());
     }
 
     private void expireArtifactsForChangingModule(ModuleVersionRepository repository, ModuleDescriptor descriptor) {
@@ -152,18 +165,6 @@ public class CachingModuleVersionRepository implements ModuleVersionRepository {
             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()) {
@@ -173,29 +174,7 @@ public class CachingModuleVersionRepository implements ModuleVersionRepository {
         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);
-        }
-
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
         ArtifactAtRepositoryKey resolutionCacheIndexKey = new ArtifactAtRepositoryKey(delegate, artifact.getId());
 
         // Look in the cache for this resolver
@@ -207,31 +186,31 @@ public class CachingModuleVersionRepository implements ModuleVersionRepository {
             if (cached.isMissing()) {
                 if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age)) {
                     LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact.getId());
-                    return null;
+                    result.notFound(artifact);
+                    return;
                 }
             } 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());
+                    result.resolved(cachedArtifactFile, cached.getExternalResourceMetaData());
+                    return;
                 }
             }
         }
 
-        DownloadedArtifact downloadedArtifact = delegate.download(artifact);
+        delegate.resolve(artifact, result);
         LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact.getId(), delegate);
 
-        if (downloadedArtifact == null) {
+        if (result.getFailure() instanceof ArtifactNotFoundException) {
             artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey);
         } else {
-            artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, downloadedArtifact.getLocalFile(), downloadedArtifact.getExternalResourceMetaData());
+            artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, result.getFile(), result.getExternalResourceMetaData());
         }
-
-        return downloadedArtifact;
     }
 
     private ModuleVersionSelector createModuleVersionSelector(ModuleRevisionId moduleRevisionId) {
-        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+        return new DefaultModuleVersionSelector(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
     }
 
     private ModuleVersionIdentifier createModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
new file mode 100644
index 0000000..ed03d4b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleVersionDescriptor {
+    private ModuleDescriptor moduleDescriptor;
+    private boolean changing;
+    private State state = State.Unknown;
+    private ModuleVersionResolveException failure;
+
+    public void reset(State state) {
+        this.state = state;
+        moduleDescriptor = null;
+        changing = false;
+        failure = null;
+    }
+
+    public void resolved(ModuleDescriptor descriptor, boolean changing) {
+        reset(State.Resolved);
+        moduleDescriptor = descriptor;
+        this.changing = changing;
+    }
+
+    public void missing() {
+        reset(State.Missing);
+    }
+
+    public void probablyMissing() {
+        reset(State.ProbablyMissing);
+    }
+
+    public void failed(ModuleVersionResolveException failure) {
+        reset(State.Failed);
+        this.failure = failure;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    private void assertHasResult() {
+        if (state == State.Unknown) {
+            throw new IllegalStateException("No result has been specified.");
+        }
+    }
+
+    private void assertResolved() {
+        if (state == State.Failed) {
+            throw failure;
+        }
+        if (state != State.Resolved) {
+            throw new IllegalStateException("This module has not been resolved.");
+        }
+    }
+
+    public ModuleRevisionId getId() {
+        assertResolved();
+        return moduleDescriptor.getResolvedModuleRevisionId();
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        assertResolved();
+        return moduleDescriptor;
+    }
+
+    public boolean isChanging() {
+        assertResolved();
+        return changing;
+    }
+}
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
index 880cfa5..41e97ce 100644
--- 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
@@ -20,9 +20,9 @@ import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
 
 class DefaultIvyAdapter implements IvyAdapter {
     private final ResolveData resolveData;
-    private final UserResolverChain userResolver;
+    private final DependencyToModuleResolver userResolver;
 
-    public DefaultIvyAdapter(ResolveData resolveData, UserResolverChain userResolverChain) {
+    public DefaultIvyAdapter(ResolveData resolveData, DependencyToModuleResolver userResolverChain) {
         this.resolveData = resolveData;
         userResolver = userResolverChain;
     }
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
deleted file mode 100644
index f74740a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionDescriptor.java
+++ /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.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/DependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverAdapter.java
deleted file mode 100644
index d8a6301..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverAdapter.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.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
index 4017b65..d7e03a4 100644
--- 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
@@ -17,8 +17,8 @@ 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.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.hash.HashUtil;
 
 import java.util.ArrayList;
@@ -56,11 +56,11 @@ public class DependencyResolverIdentifier {
     }
 
     private String joinPatterns(List<String> patterns) {
-        return GUtil.join(patterns, ",");
+        return CollectionUtils.join(",", patterns);
     }
 
     private String calculateId(List<String> parts) {
-        String idString = GUtil.join(parts, "::");
+        String idString = CollectionUtils.join("::", parts);
         return HashUtil.createHash(idString, "MD5").asHexString();
     }
 
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
deleted file mode 100644
index 91abceb..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DownloadedArtifact.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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/ExternalResourceResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
new file mode 100644
index 0000000..7a67340
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.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.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+/**
+ * A {@link ModuleVersionRepository} wrapper around an {@link ExternalResourceResolver}.
+ */
+public class ExternalResourceResolverAdapter extends AbstractDependencyResolverAdapter {
+    private final ExternalResourceResolver resolver;
+
+    public ExternalResourceResolverAdapter(ExternalResourceResolver resolver) {
+        super(resolver);
+        this.resolver = resolver;
+    }
+
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+        EnhancedArtifactDownloadReport artifactDownloadReport = resolver.download(artifact);
+        if (downloadFailed(artifactDownloadReport)) {
+            result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getFailure()));
+            return;
+        }
+
+        ArtifactOriginWithMetaData artifactOrigin = artifactDownloadReport.getArtifactOrigin();
+
+        File localFile = artifactDownloadReport.getLocalFile();
+        if (localFile != null) {
+            ExternalResourceMetaData metaData = artifactOrigin.getMetaData();
+            result.resolved(localFile, metaData);
+        } else {
+            result.notFound(artifact);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
new file mode 100644
index 0000000..92dba7b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.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.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+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.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+/**
+ * A {@link ModuleVersionRepository} wrapper around an Ivy {@link DependencyResolver}.
+ */
+public class IvyDependencyResolverAdapter extends AbstractDependencyResolverAdapter {
+    private final DownloadOptions downloadOptions = new DownloadOptions();
+
+    public IvyDependencyResolverAdapter(DependencyResolver resolver) {
+        super(resolver);
+    }
+
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+        ArtifactDownloadReport artifactDownloadReport = resolver.download(new Artifact[]{artifact}, downloadOptions).getArtifactReport(artifact);
+        if (downloadFailed(artifactDownloadReport)) {
+            if (artifactDownloadReport instanceof EnhancedArtifactDownloadReport) {
+                EnhancedArtifactDownloadReport enhancedReport = (EnhancedArtifactDownloadReport) artifactDownloadReport;
+                result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), enhancedReport.getFailure()));
+            } else {
+                result.failed(new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getDownloadDetails()));
+            }
+            return;
+        }
+
+        ArtifactOrigin artifactOrigin = artifactDownloadReport.getArtifactOrigin();
+
+        File localFile = artifactDownloadReport.getLocalFile();
+        if (localFile != null) {
+            ExternalResourceMetaData metaData = null;
+            if (artifactOrigin instanceof ArtifactOriginWithMetaData) {
+                metaData = ((ArtifactOriginWithMetaData) artifactOrigin).getMetaData();
+            }
+            result.resolved(localFile, metaData);
+        } else {
+            result.notFound(artifact);
+        }
+    }
+}
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
index 96dd754..b9304a0 100755
--- 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
@@ -53,44 +53,18 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             this.resolver = resolver;
         }
 
-        public ArtifactResolveResult resolve(Artifact artifact) throws ArtifactResolveException {
-            ArtifactResolveResult result;
+        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
             try {
-                result = resolver.resolve(artifact);
+                resolver.resolve(artifact, result);
             } catch (Throwable t) {
-                return new BrokenArtifactResolveResult(new ArtifactResolveException(artifact, t));
+                result.failed(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;
+        private BuildableModuleVersionResolveResult resolveResult;
 
         public StaticVersionResolveResult(DependencyDescriptor dependencyDescriptor) {
             this.dependencyDescriptor = dependencyDescriptor;
@@ -106,11 +80,10 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
 
         public ModuleVersionResolveResult resolve() {
             if (resolveResult == null) {
-                ModuleVersionResolveException failure = null;
-                ModuleVersionResolveResult resolveResult = null;
+                resolveResult = new DefaultBuildableModuleVersionResolveResult();
                 try {
                     try {
-                        resolveResult = dependencyResolver.resolve(dependencyDescriptor);
+                        dependencyResolver.resolve(dependencyDescriptor, resolveResult);
                     } catch (Throwable t) {
                         throw new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), t);
                     }
@@ -121,20 +94,19 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
                         throw resolveResult.getFailure();
                     }
                     checkDescriptor(resolveResult.getDescriptor());
+                    resolveResult.setArtifactResolver(new ErrorHandlingArtifactResolver(resolveResult.getArtifactResolver()));
                 } catch (ModuleVersionResolveException e) {
-                    failure = e;
-                }
-
-                if (failure != null) {
-                    this.resolveResult = new BrokenModuleVersionResolveResult(failure);
-                } else {
-                    this.resolveResult = new DefaultModuleVersionResolveResult(resolveResult);
+                    resolveResult.failed(e);
                 }
             }
 
             return resolveResult;
         }
 
+        public IdSelectionReason getSelectionReason() {
+            return IdSelectionReason.requested;
+        }
+
         private void checkDescriptor(ModuleDescriptor descriptor) {
             ModuleRevisionId id = descriptor.getModuleRevisionId();
             if (!copy(id).equals(copy(dependencyDescriptor.getDependencyRevisionId()))) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java
new file mode 100644
index 0000000..670dce9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.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.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+
+public interface LocalAwareModuleVersionRepository extends ModuleVersionRepository {
+    /**
+     * Locates the given dependency, using only local resources.
+     */
+    void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
+
+    /**
+     * Locates the given dependency, using whichever resources are appropriate. Always called after {@link #getLocalDependency(org.apache.ivy.core.module.descriptor.DependencyDescriptor, BuildableModuleVersionDescriptor)}.
+     */
+    void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
new file mode 100644
index 0000000..4391321
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.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.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.BuildableArtifactResolveResult;
+
+public class LocalModuleVersionRepository implements LocalAwareModuleVersionRepository {
+    private final ModuleVersionRepository delegate;
+
+    public LocalModuleVersionRepository(ModuleVersionRepository delegate) {
+        this.delegate = delegate;
+    }
+
+    public boolean isLocal() {
+        return delegate.isLocal();
+    }
+
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+        delegate.resolve(artifact, result);
+    }
+
+    public void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        delegate.getDependency(dependencyDescriptor, result);
+    }
+
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+        result.missing();
+    }
+
+    public String getId() {
+        return delegate.getId();
+    }
+
+    public String getName() {
+        return delegate.getName();
+    }
+}
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
index b37424e..65ba6cd 100644
--- 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
@@ -25,8 +25,9 @@ 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.DefaultBuildableArtifactResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultBuildableModuleVersionResolveResult;
 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;
@@ -61,11 +62,11 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
         final DependencyResolver loopback = this;
         return cacheLockingManager.useCache(String.format("Resolve %s", dd), new Factory<ResolvedModuleRevision>() {
             public ResolvedModuleRevision create() {
-                ModuleVersionResolveResult dependency;
+                DefaultBuildableModuleVersionResolveResult dependency = new DefaultBuildableModuleVersionResolveResult();
                 IvyContext ivyContext = IvyContext.pushNewCopyContext();
                 try {
                     ivyContext.setResolveData(data);
-                    dependency = userResolverChain.resolve(dd);
+                    userResolverChain.resolve(dd, dependency);
                 } finally {
                     IvyContext.popContext();
                 }
@@ -81,7 +82,11 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
             public ArtifactOrigin create() {
                 try {
                     DependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(artifact.getModuleRevisionId(), false);
-                    File artifactFile = userResolverChain.resolve(dependencyDescriptor).getArtifactResolver().resolve(artifact).getFile();
+                    DefaultBuildableModuleVersionResolveResult dependency = new DefaultBuildableModuleVersionResolveResult();
+                    userResolverChain.resolve(dependencyDescriptor, dependency);
+                    DefaultBuildableArtifactResolveResult result = new DefaultBuildableArtifactResolveResult();
+                    dependency.getArtifactResolver().resolve(artifact, result);
+                    File artifactFile = result.getFile();
                     return new ArtifactOrigin(artifact, false, artifactFile.getAbsolutePath());
                 } catch (ModuleVersionNotFoundException 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
index 23a20f0..4fd523d 100644
--- 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 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.
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 
+// at some point, this is due to merge with ModuleVersionResolveResult
 public interface ModuleVersionDescriptor {
     ModuleRevisionId getId();
 
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
index 4233e19..d77c451 100644
--- 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
@@ -17,29 +17,26 @@ 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;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 
 /**
  * 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}.
+ * The plan is to sync this with {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver}
  */
-public interface ModuleVersionRepository {
+public interface ModuleVersionRepository extends ArtifactResolver {
     String getId();
 
     String getName();
 
-    /**
-     * @return null if not found.
-     */
-    ModuleVersionDescriptor getDependency(DependencyDescriptor dd) throws ModuleVersionResolveException;
+    void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
 
     /**
-     * @return null if not found.
+     * Downloads the given artifact. Any failures are packaged up in the result.
      */
-    DownloadedArtifact download(Artifact artifact) throws ArtifactResolveException;
+    void resolve(Artifact artifact, BuildableArtifactResolveResult result);
 
-    // TODO - should be internal to the implementation of this (is only used to communicate DependencyResolverAdapter -> CachingModuleVersionRepository)
+    // TODO - should be internal to the implementation of this (is only used to communicate IvyDependencyResolverAdapter -> CachingModuleVersionRepository)
     boolean isLocal();
 }
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
index 5e3aac1..f72ec34 100644
--- 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
@@ -26,10 +26,11 @@ 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.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
 import org.gradle.internal.TimeProvider;
 import org.gradle.util.WrapUtil;
 
@@ -46,7 +47,6 @@ public class ResolveIvyFactory {
     private final StartParameterResolutionOverride startParameterResolutionOverride;
     private final TimeProvider timeProvider;
 
-
     public ResolveIvyFactory(IvyFactory ivyFactory, ResolverProvider resolverProvider, SettingsConverter settingsConverter,
                              ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
                              CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex,
@@ -74,26 +74,32 @@ public class ResolveIvyFactory {
         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 moduleVersionRepository;
+            if (rawResolver instanceof ExternalResourceResolver) {
+                moduleVersionRepository = new ExternalResourceResolverAdapter((ExternalResourceResolver) rawResolver);
+            } else {
+                moduleVersionRepository = new IvyDependencyResolverAdapter(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);
+            LocalAwareModuleVersionRepository cachingRepository;
+            if (moduleVersionRepository.isLocal()) {
+                cachingRepository = new LocalModuleVersionRepository(moduleVersionRepository);
+            } else {
+                cachingRepository = new CachingModuleVersionRepository(moduleVersionRepository, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCachedResolutionIndex,
+                        configuration.getResolutionStrategy().getCachePolicy(), timeProvider);
+            }
+            LocalAwareModuleVersionRepository ivyContextualisedRepository = contextualiser.contextualise(LocalAwareModuleVersionRepository.class, cachingRepository);
             userResolverChain.add(ivyContextualisedRepository);
         }
 
         return new DefaultIvyAdapter(resolveData, userResolverChain);
     }
-    
+
     private ResolveData createResolveData(Ivy ivy, String configurationName) {
         ResolveOptions options = new ResolveOptions();
         options.setDownload(false);
@@ -101,5 +107,5 @@ public class ResolveIvyFactory {
         return new ResolveData(ivy.getResolveEngine(), options);
     }
 
-    
+
 }
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
index 69a1cff..30f5aa7 100644
--- 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
@@ -23,6 +23,7 @@ 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.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 
 import java.util.concurrent.TimeUnit;
@@ -96,12 +97,12 @@ public class StartParameterResolutionOverride {
             return false;
         }
 
-        public ModuleVersionDescriptor getDependency(DependencyDescriptor dd) {
-            throw new ModuleVersionResolveException("No cached version available for offline mode");
+        public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+            result.failed(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");
+        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
+            result.failed(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
index d0f929e..dd0b78a 100644
--- 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
@@ -16,26 +16,24 @@
 
 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.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
+import java.util.*;
 
 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<LocalAwareModuleVersionRepository> moduleVersionRepositories = new ArrayList<LocalAwareModuleVersionRepository>();
     private final List<String> moduleVersionRepositoryNames = new ArrayList<String>();
     private ResolverSettings settings;
 
@@ -43,43 +41,85 @@ public class UserResolverChain implements DependencyToModuleResolver {
         this.settings = settings;
     }
 
-    public void add(ModuleVersionRepository repository) {
+    public void add(LocalAwareModuleVersionRepository repository) {
         moduleVersionRepositories.add(repository);
         moduleVersionRepositoryNames.add(repository.getName());
     }
 
-    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
-        LOGGER.debug("Attempting to resolve module '{}' using repositories '{}'", dependencyDescriptor.getDependencyRevisionId(), moduleVersionRepositoryNames);
+    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
+        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;
+            for (Throwable error : errors) {
+                LOGGER.debug("Discarding resolve failure.", error);
+            }
+            result.resolved(latestResolved.getId(), latestResolved.getDescriptor(), latestResolved.repository);
+            return;
         }
         if (!errors.isEmpty()) {
-            return new BrokenModuleVersionResolveResult(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), errors));
+            result.failed(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), errors));
+        } else {
+            result.notFound(dependencyDescriptor.getDependencyRevisionId());
         }
-        
-        return new BrokenModuleVersionResolveResult(new ModuleVersionNotFoundException(dependencyDescriptor.getDependencyRevisionId()));
     }
 
     private ModuleResolution findLatestModule(DependencyDescriptor dependencyDescriptor, Collection<Throwable> failures) {
+        LinkedList<RepositoryResolveState> queue = new LinkedList<RepositoryResolveState>();
+        for (LocalAwareModuleVersionRepository repository : moduleVersionRepositories) {
+            queue.add(new RepositoryResolveState(repository));
+        }
+        LinkedList<RepositoryResolveState> missing = new LinkedList<RepositoryResolveState>();
+
+        // A first pass to do local resolves only
+        ModuleResolution best = findLatestModule(dependencyDescriptor, queue, failures, missing);
+        if (best != null) {
+            return best;
+        }
+
+        // Nothing found - do a second pass
+        queue.addAll(missing);
+        missing.clear();
+        return findLatestModule(dependencyDescriptor, queue, failures, missing);
+    }
+
+    private ModuleResolution findLatestModule(DependencyDescriptor dependencyDescriptor, LinkedList<RepositoryResolveState> queue, Collection<Throwable> failures, Collection<RepositoryResolveState> missing) {
         boolean isStaticVersion = !settings.getVersionMatcher().isDynamic(dependencyDescriptor.getDependencyRevisionId());
-        
         ModuleResolution best = null;
-        for (ModuleVersionRepository repository : moduleVersionRepositories) {
+        while (!queue.isEmpty()) {
+            RepositoryResolveState request = queue.removeFirst();
             try {
-                ModuleVersionDescriptor module = repository.getDependency(dependencyDescriptor);
-                if (module != null) {
-                    ModuleResolution moduleResolution = new ModuleResolution(repository, module);
+                request.resolve(dependencyDescriptor);
+            } catch (Throwable t) {
+                failures.add(t);
+                continue;
+            }
+            switch (request.descriptor.getState()) {
+                case Missing:
+                    break;
+                case ProbablyMissing:
+                    // Queue this up for checking again later
+                    if (request.canMakeFurtherAttempts()) {
+                        missing.add(request);
+                    }
+                    break;
+                case Unknown:
+                    // Resolve again now
+                    if (request.canMakeFurtherAttempts()) {
+                        queue.addFirst(request);
+                    }
+                    break;
+                case Resolved:
+                    ModuleResolution moduleResolution = new ModuleResolution(request.repository, request.descriptor);
                     if (isStaticVersion && !moduleResolution.isGeneratedModuleDescriptor()) {
                         return moduleResolution;
                     }
                     best = chooseBest(best, moduleResolution);
-                }
-            } catch (Throwable e) {
-                failures.add(e);
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected state for resolution: " + request.descriptor.getState());
             }
         }
 
@@ -108,7 +148,35 @@ public class UserResolverChain implements DependencyToModuleResolver {
         return comparison < 0 ? two : one;
     }
 
-    private static class ModuleResolution implements ArtifactInfo, ModuleVersionResolveResult {
+    private static class RepositoryResolveState {
+        final LocalAwareModuleVersionRepository repository;
+        final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor();
+        boolean searchedLocally;
+        boolean searchedRemotely;
+
+        private RepositoryResolveState(LocalAwareModuleVersionRepository repository) {
+            this.repository = repository;
+        }
+
+        void resolve(DependencyDescriptor dependencyDescriptor) {
+            if (!searchedLocally) {
+                searchedLocally = true;
+                repository.getLocalDependency(dependencyDescriptor, descriptor);
+            } else {
+                searchedRemotely = true;
+                repository.getDependency(dependencyDescriptor, descriptor);
+            }
+            if (descriptor.getState() == BuildableModuleVersionDescriptor.State.Failed) {
+                throw descriptor.getFailure();
+            }
+        }
+
+        public boolean canMakeFurtherAttempts() {
+            return !searchedRemotely;
+        }
+    }
+
+    private static class ModuleResolution implements ArtifactInfo {
         public final ModuleVersionRepository repository;
         public final ModuleVersionDescriptor module;
 
@@ -117,10 +185,6 @@ public class UserResolverChain implements DependencyToModuleResolver {
             this.module = module;
         }
 
-        public ModuleVersionResolveException getFailure() {
-            return null;
-        }
-
         public ModuleRevisionId getId() throws ModuleVersionResolveException {
             return module.getId();
         }
@@ -129,14 +193,7 @@ public class UserResolverChain implements DependencyToModuleResolver {
             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();
         }
 
@@ -148,26 +205,4 @@ public class UserResolverChain implements DependencyToModuleResolver {
             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/DownloadedIvyModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
index 33b926d..cb5c3aa 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
@@ -28,7 +28,7 @@ import java.text.ParseException;
 
 public class DownloadedIvyModuleDescriptorParser extends XmlModuleDescriptorParser {
     @Override
-    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
+    synchronized public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
         DefaultModuleDescriptor descriptor = (DefaultModuleDescriptor) super.parseDescriptor(ivySettings, xmlURL, res, validate);
         descriptor.setDefault(false);
         return descriptor;
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
index 49390e2..e0eceaa 100644
--- 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
@@ -17,18 +17,8 @@ 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.*;
 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;
@@ -44,16 +34,7 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.util.Message;
 import org.gradle.util.DeprecationLogger;
 
-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.*;
 import java.util.Map.Entry;
 
 
@@ -260,8 +241,7 @@ public class GradlePomModuleDescriptorBuilder {
                     mainArtifact = artifact;
                     ivyModuleDescriptor.addArtifact("master", mainArtifact);
 
-                    DeprecationLogger.nagUserWith("Deprecated: relying on packaging to define the extension of the main artifact is deprecated, "
-                            + "and will not be supported in a future version of Gradle.");
+                    DeprecationLogger.nagUserOfDeprecated("Relying on packaging to define the extension of the main artifact");
 
                     return;
                 }
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
index 02c5d65..8762833 100644
--- 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
@@ -28,7 +28,6 @@ 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;
@@ -51,13 +50,7 @@ import java.util.Map;
 public final class GradlePomModuleDescriptorParser implements ModuleDescriptorParser {
     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();
-            }
-        }
+        throw new UnsupportedOperationException();
     }
 
     public boolean accept(Resource res) {
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
index e86c418..523d029 100644
--- 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
@@ -17,9 +17,12 @@ 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.xml.XmlModuleDescriptorParser;
 import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.IvyXmlModuleDescriptorWriter;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.internal.TimeProvider;
 import org.slf4j.Logger;
@@ -44,7 +47,7 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
         this.cacheMetadata = cacheMetadata;
 
         // TODO:DAZ inject this
-        moduleDescriptorStore = new ModuleDescriptorStore(new ModuleDescriptorFileStore(cacheMetadata));
+        moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheMetadata.getCacheDir()), new IvyXmlModuleDescriptorWriter(), XmlModuleDescriptorParser.getInstance());
     }
 
     private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> getCache() {
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
deleted file mode 100644
index 4bf0d7f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorFileStore.java
+++ /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.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
index 08b1337..334a4d5 100644
--- 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
@@ -15,36 +15,64 @@
  */
 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.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.Action;
+import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
 import org.gradle.internal.UncheckedException;
 
 import java.io.File;
 import java.net.URL;
+import java.util.Collections;
 
 public class ModuleDescriptorStore {
 
-    private final ModuleDescriptorFileStore moduleDescriptorFileStore;
-    private final XmlModuleDescriptorParser parser = XmlModuleDescriptorParser.getInstance();
+    private static final String DESCRIPTOR_ARTIFACT_PATTERN =
+            "module-metadata/[organisation]/[module](/[branch])/[revision]/[resolverId].ivy.xml";
 
-    public ModuleDescriptorStore(ModuleDescriptorFileStore moduleDescriptorFileStore) {
-        this.moduleDescriptorFileStore = moduleDescriptorFileStore;
+    private final XmlModuleDescriptorParser parser;
+    private final PathKeyFileStore pathKeyFileStore;
+    private final IvyModuleDescriptorWriter ivyModuleDescriptorWriter;
+
+    public ModuleDescriptorStore(PathKeyFileStore pathKeyFileStore, IvyModuleDescriptorWriter ivyModuleDescriptorWriter, XmlModuleDescriptorParser xmlModuleDescriptorParser) {
+        this.pathKeyFileStore = pathKeyFileStore;
+        this.ivyModuleDescriptorWriter = ivyModuleDescriptorWriter;
+        parser = xmlModuleDescriptorParser;
     }
 
     public ModuleDescriptor getModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
-        File moduleDescriptorFile = moduleDescriptorFileStore.getModuleDescriptorFile(repository, moduleRevisionId);
-        if (moduleDescriptorFile.exists()) {
-            return parseModuleDescriptorFile(moduleDescriptorFile);
+        String filePath = getFilePath(repository, moduleRevisionId);
+        final FileStoreEntry fileStoreEntry = pathKeyFileStore.get(filePath);
+        if (fileStoreEntry != null) {
+            return parseModuleDescriptorFile(fileStoreEntry.getFile());
         }
         return null;
     }
 
-    private ModuleDescriptor parseModuleDescriptorFile(File moduleDescriptorFile)  {
+
+    public void putModuleDescriptor(ModuleVersionRepository repository, final ModuleDescriptor moduleDescriptor) {
+        String filePath = getFilePath(repository, moduleDescriptor.getModuleRevisionId());
+        pathKeyFileStore.add(filePath, new Action<File>() {
+            public void execute(File moduleDescriptorFile) {
+                try {
+                    ivyModuleDescriptorWriter.write(moduleDescriptor, moduleDescriptorFile);
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        });
+    }
+
+    private ModuleDescriptor parseModuleDescriptorFile(File moduleDescriptorFile) {
         ParserSettings settings = IvyContextualiser.getIvyContext().getSettings();
         try {
             URL result = moduleDescriptorFile.toURI().toURL();
@@ -54,12 +82,9 @@ public class ModuleDescriptorStore {
         }
     }
 
-    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);
-        }
+    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/projectmodule/ProjectDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
index fcd7e5f..5edc41a 100644
--- 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
@@ -18,9 +18,7 @@ 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;
 
@@ -35,46 +33,19 @@ public class ProjectDependencyResolver implements DependencyToModuleResolver {
         artifactResolver = new ProjectArtifactResolver();
     }
 
-    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
         ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(dependencyDescriptor);
-
-        if (moduleDescriptor == null) {
-            return resolver.resolve(dependencyDescriptor);
+        if (moduleDescriptor != null) {
+            result.resolved(moduleDescriptor.getModuleRevisionId(), moduleDescriptor, artifactResolver);
+        } else {
+            resolver.resolve(dependencyDescriptor, result);
         }
-
-        return new ProjectDependencyModuleVersionResolveResult(moduleDescriptor, artifactResolver);
     }
 
     private static class ProjectArtifactResolver implements ArtifactResolver {
-        public ArtifactResolveResult resolve(Artifact artifact) throws ArtifactResolveException {
+        public void resolve(Artifact artifact, BuildableArtifactResolveResult result) {
             String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_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;
+            result.resolved(new File(path), null);
         }
     }
 }
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
index 8f2d780..a69f82f 100755
--- 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
@@ -16,8 +16,8 @@
 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.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.configurations.conflicts.StrictConflictResolution;
 import org.gradle.api.internal.artifacts.ivyservice.*;
@@ -27,6 +27,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LazyDependencyToM
 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.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,7 +46,7 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         this.projectModuleRegistry = projectModuleRegistry;
     }
 
-    public ResolvedConfiguration resolve(ConfigurationInternal configuration) throws ResolveException {
+    public ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException {
         LOGGER.debug("Resolving {}", configuration);
 
         IvyAdapter ivyAdapter = ivyFactory.create(configuration);
@@ -64,7 +65,8 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         }
 
         DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, idResolver, conflictResolver);
-        DefaultLenientConfiguration result = builder.resolve(configuration, ivyAdapter.getResolveData());
-        return new DefaultResolvedConfiguration(result);
+        ResolutionResultBuilder resultBuilder = new ResolutionResultBuilder();
+        DefaultLenientConfiguration result = builder.resolve(configuration, ivyAdapter.getResolveData(), resultBuilder);
+        return new ResolverResults(new DefaultResolvedConfiguration(result), resultBuilder.getResult());
     }
 }
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
index 73865e1..777ff59 100644
--- 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
@@ -20,18 +20,29 @@ 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.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 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.gradle.api.internal.artifacts.ivyservice.resolveengine.result.InternalDependencyResult;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ModuleVersionSelection;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolvedConfigurationListener;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.*;
 
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId;
+
 public class DependencyGraphBuilder {
     private static final Logger LOGGER = LoggerFactory.getLogger(DependencyGraphBuilder.class);
     private final ModuleDescriptorConverter moduleDescriptorConverter;
@@ -46,14 +57,14 @@ public class DependencyGraphBuilder {
         this.conflictResolver = new ForcedModuleConflictResolver(conflictResolver);
     }
 
-    public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData) throws ResolveException {
+    public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData, ResolvedConfigurationListener listener) 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);
+        assembleResult(resolveState, result, listener);
 
         return result;
     }
@@ -134,11 +145,22 @@ public class DependencyGraphBuilder {
     /**
      * Populates the result from the graph traversal state.
      */
-    private void assembleResult(ResolveState resolveState, ResolvedConfigurationBuilder result) {
+    private void assembleResult(ResolveState resolveState, ResolvedConfigurationBuilder result, ResolvedConfigurationListener listener) {
         FailureState failureState = new FailureState(resolveState.root);
+        ModuleVersionIdentifier root = resolveState.root.toId();
+        listener.start(root);
+
+        for (ConfigurationNode resolvedConfiguration : resolveState.getConfigurationNodes()) {
+            if (resolvedConfiguration.isSelected()) {
+                resolvedConfiguration.attachToParents(resolvedArtifactFactory, result);
+                resolvedConfiguration.collectFailures(failureState);
+                listener.resolvedModuleVersion(resolvedConfiguration.moduleRevision);
+            }
+        }
         for (ConfigurationNode resolvedConfiguration : resolveState.getConfigurationNodes()) {
-            resolvedConfiguration.attachToParents(resolvedArtifactFactory, result);
-            resolvedConfiguration.collectFailures(failureState);
+            if (resolvedConfiguration.isSelected()) {
+                listener.resolvedConfiguration(resolvedConfiguration.toId(), resolvedConfiguration.outgoingEdges);
+            }
         }
         failureState.attachFailures(result);
     }
@@ -237,7 +259,7 @@ public class DependencyGraphBuilder {
         }
     }
 
-    private static class DependencyEdge {
+    private static class DependencyEdge implements InternalDependencyResult {
         private final ConfigurationNode from;
         private final DependencyDescriptor dependencyDescriptor;
         private final Set<String> targetConfigurationRules;
@@ -371,11 +393,31 @@ public class DependencyGraphBuilder {
             return selector.intersect(selectorSpec);
         }
 
+        public boolean isFailed() {
+            return selector != null && selector.failure != null;
+        }
+
+        public ModuleVersionSelector getRequested() {
+            return new DefaultModuleVersionSelector(
+                    dependencyDescriptor.getDependencyRevisionId().getOrganisation(),
+                    dependencyDescriptor.getDependencyRevisionId().getName(),
+                    dependencyDescriptor.getDependencyRevisionId().getRevision());
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return selector.failure;
+        }
+
+        public ModuleVersionSelection getSelected() {
+            return selector.module.selected;
+        }
+
         public void collectFailures(FailureState failureState) {
-            if (selector != null && selector.failure != null) {
-                failureState.addUnresolvedDependency(this, selector.descriptor.getDependencyRevisionId(), selector.failure);
+            if (isFailed()) {
+                failureState.addUnresolvedDependency(this, selector.descriptor.getDependencyRevisionId(), getFailure());
             }
         }
+
     }
 
     private static class ResolveState {
@@ -541,7 +583,7 @@ public class DependencyGraphBuilder {
         }
     }
 
-    private static class DefaultModuleRevisionResolveState implements ModuleRevisionResolveState {
+    private static class DefaultModuleRevisionResolveState implements ModuleRevisionResolveState, ModuleVersionSelection {
         final ModuleResolveState module;
         final ModuleRevisionId id;
         final ResolveState resolveState;
@@ -550,6 +592,7 @@ public class DependencyGraphBuilder {
         ModuleDescriptor descriptor;
         ModuleState state = ModuleState.New;
         ModuleVersionSelectorResolveState resolver;
+        ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED;
 
         private DefaultModuleRevisionResolveState(ModuleResolveState module, ModuleRevisionId id, ResolveState resolveState) {
             this.module = module;
@@ -608,6 +651,17 @@ public class DependencyGraphBuilder {
                 this.descriptor = descriptor;
             }
         }
+
+        public ModuleVersionIdentifier getSelectedId() {
+            return new DefaultModuleVersionIdentifier(
+                    id.getOrganisation(),
+                    id.getName(),
+                    id.getRevision());
+        }
+
+        public ModuleVersionSelectionReason getSelectionReason() {
+            return selectionReason;
+        }
     }
 
     private static class ConfigurationNode {
@@ -754,11 +808,11 @@ public class DependencyGraphBuilder {
             resolveState.onFewerSelected(this);
         }
 
+        public boolean isSelected() {
+            return moduleRevision.state == ModuleState.Selected;
+        }
+
         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);
@@ -766,9 +820,6 @@ public class DependencyGraphBuilder {
         }
 
         public void collectFailures(FailureState failureState) {
-            if (moduleRevision.state != ModuleState.Selected) {
-                return;
-            }
             for (DependencyEdge dependency : outgoingEdges) {
                 dependency.collectFailures(failureState);
             }
@@ -811,6 +862,12 @@ public class DependencyGraphBuilder {
                 incomingEdges.clear();
             }
         }
+
+        private ModuleVersionIdentifier toId() {
+            return newId(moduleRevision.id.getOrganisation(),
+                    moduleRevision.id.getName(),
+                    moduleRevision.id.getRevision());
+        }
     }
 
     private static class ModuleVersionSelectorResolveState {
@@ -840,6 +897,10 @@ public class DependencyGraphBuilder {
          */
         public DefaultModuleRevisionResolveState resolveModuleRevisionId() {
             if (targetModuleRevision != null) {
+                //(SF) this might not be quite right
+                //this.targetModuleRevision might have been evicted in an earlier pass of conflict resolution
+                //and the module.selected has the actual target module.
+                //I'm not sure how big deal is it.
                 return targetModuleRevision;
             }
             if (failure != null) {
@@ -854,6 +915,11 @@ public class DependencyGraphBuilder {
 
             targetModuleRevision = resolveState.getRevision(idResolveResult.getId());
             targetModuleRevision.addResolver(this);
+
+            if (idResolveResult.getSelectionReason() == ModuleVersionIdResolveResult.IdSelectionReason.forced) {
+                targetModuleRevision.selectionReason = VersionSelectionReasons.FORCED;
+            }
+
             return targetModuleRevision;
         }
 
@@ -890,11 +956,15 @@ public class DependencyGraphBuilder {
             for (ConfigurationNode configuration : root.configurations) {
                 for (DependencyEdge outgoingEdge : configuration.outgoingEdges) {
                     if (outgoingEdge.dependencyDescriptor.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) {
+                        outgoingEdge.targetModuleRevision.selectionReason = VersionSelectionReasons.FORCED;
                         return outgoingEdge.targetModuleRevision;
                     }
                 }
             }
-            return (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
+            //TODO SF unit test
+            DefaultModuleRevisionResolveState out = (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
+            out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION;
+            return out;
         }
     }
 }
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
index ad368a2..14be289 100644
--- 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
@@ -15,36 +15,23 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
-import org.apache.ivy.plugins.latest.ArtifactInfo;
-import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
+import org.gradle.api.internal.artifacts.version.LatestVersionSemanticComparator;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
+import java.util.Collections;
+import java.util.Comparator;
 
 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;
+        return Collections.max(candidates, new VersionComparator());
     }
 
-    private static class ModuleResolveStateBackedArtifactInfo implements ArtifactInfo {
-        final ModuleRevisionResolveState moduleRevision;
-
-        public ModuleResolveStateBackedArtifactInfo(ModuleRevisionResolveState moduleRevision) {
-            this.moduleRevision = moduleRevision;
-        }
+    private class VersionComparator implements Comparator<ModuleRevisionResolveState> {
 
-        public String getRevision() {
-            return moduleRevision.getRevision();
-        }
+        LatestVersionSemanticComparator delegate = new LatestVersionSemanticComparator();
 
-        public long getLastModified() {
-            throw new UnsupportedOperationException();
+        public int compare(ModuleRevisionResolveState left, ModuleRevisionResolveState right) {
+            return delegate.compare(left.getRevision(), right.getRevision());
         }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.java
new file mode 100644
index 0000000..d226e57
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactory.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.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult;
+import org.gradle.api.internal.artifacts.result.DefaultUnresolvedDependencyResult;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
+/**
+ * by Szczepan Faber, created at: 10/1/12
+ */
+public class CachingDependencyResultFactory {
+
+    Map<List, DefaultUnresolvedDependencyResult> unresolvedDependencies = new LinkedHashMap();
+    Map<List, DefaultResolvedDependencyResult> resolvedDependencies = new LinkedHashMap();
+
+    public DependencyResult createUnresolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, Exception failure) {
+        List<Object> key = asList(requested, from);
+        if (!unresolvedDependencies.containsKey(key)) {
+            unresolvedDependencies.put(key, new DefaultUnresolvedDependencyResult(requested, failure, from));
+        }
+        return unresolvedDependencies.get(key);
+    }
+
+    public DependencyResult createResolvedDependency(ModuleVersionSelector requested, ResolvedModuleVersionResult from, ResolvedModuleVersionResult selected) {
+        List<Object> key = asList(requested, from, selected);
+        if (!resolvedDependencies.containsKey(key)) {
+            resolvedDependencies.put(key, new DefaultResolvedDependencyResult(requested, selected, from));
+        }
+        return resolvedDependencies.get(key);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.java
new file mode 100644
index 0000000..4ad0280
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/InternalDependencyResult.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.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+/**
+ * by Szczepan Faber, created at: 8/24/12
+ */
+public interface InternalDependencyResult {
+
+    ModuleVersionSelector getRequested();
+
+    @Nullable Exception getFailure();
+
+    @Nullable ModuleVersionSelection getSelected();
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelection.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelection.java
new file mode 100644
index 0000000..bf702f4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ModuleVersionSelection.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.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
+/**
+* by Szczepan Faber, created at: 8/31/12
+*/
+public interface ModuleVersionSelection {
+
+    ModuleVersionIdentifier getSelectedId();
+
+    ModuleVersionSelectionReason getSelectionReason();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.java
new file mode 100644
index 0000000..eadfc18
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilder.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.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
+import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 7/26/12
+ */
+public class ResolutionResultBuilder implements ResolvedConfigurationListener {
+
+    private DefaultResolvedModuleVersionResult rootModule;
+
+    private Map<ModuleVersionIdentifier, DefaultResolvedModuleVersionResult> modules
+            = new LinkedHashMap<ModuleVersionIdentifier, DefaultResolvedModuleVersionResult>();
+
+    CachingDependencyResultFactory dependencyResultFactory = new CachingDependencyResultFactory();
+
+    public ResolutionResultBuilder start(ModuleVersionIdentifier root) {
+        rootModule = createOrGet(root, VersionSelectionReasons.ROOT);
+        return this;
+    }
+
+    public DefaultResolutionResult getResult() {
+        return new DefaultResolutionResult(rootModule);
+    }
+
+    public void resolvedModuleVersion(ModuleVersionSelection moduleVersion) {
+        createOrGet(moduleVersion.getSelectedId(), moduleVersion.getSelectionReason());
+    }
+
+    public void resolvedConfiguration(ModuleVersionIdentifier id, Collection<? extends InternalDependencyResult> dependencies) {
+        for (InternalDependencyResult d : dependencies) {
+            DefaultResolvedModuleVersionResult from = modules.get(id);
+            if (d.getFailure() != null) {
+                from.addDependency(dependencyResultFactory.createUnresolvedDependency(d.getRequested(), from, d.getFailure()));
+            } else {
+                DefaultResolvedModuleVersionResult selected = modules.get(d.getSelected().getSelectedId());
+                DependencyResult dependency = dependencyResultFactory.createResolvedDependency(d.getRequested(), from, selected);
+                from.addDependency(dependency);
+                selected.addDependent((ResolvedDependencyResult) dependency);
+            }
+        }
+    }
+
+    private DefaultResolvedModuleVersionResult createOrGet(ModuleVersionIdentifier id, ModuleVersionSelectionReason selectionReason) {
+        if (!modules.containsKey(id)) {
+            modules.put(id, new DefaultResolvedModuleVersionResult(id, selectionReason));
+        }
+        return modules.get(id);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolvedConfigurationListener.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolvedConfigurationListener.java
new file mode 100644
index 0000000..1ac0e97
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolvedConfigurationListener.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.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 7/26/12
+ */
+public interface ResolvedConfigurationListener {
+    ResolvedConfigurationListener start(ModuleVersionIdentifier root);
+    void resolvedModuleVersion(ModuleVersionSelection moduleVersion);
+    void resolvedConfiguration(ModuleVersionIdentifier id, Collection<? extends InternalDependencyResult> dependencies);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
new file mode 100644
index 0000000..897d35b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.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.api.internal.artifacts.ivyservice.resolveengine.result;
+
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
+/**
+ * by Szczepan Faber, created at: 10/1/12
+ */
+public class VersionSelectionReasons {
+    public static final ModuleVersionSelectionReason REQUESTED = new DefaultModuleVersionSelectionReason(false, false);
+    public static final ModuleVersionSelectionReason ROOT = new DefaultModuleVersionSelectionReason(false, false);
+    public static final ModuleVersionSelectionReason FORCED = new DefaultModuleVersionSelectionReason(true, false);
+    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION = new DefaultModuleVersionSelectionReason(false, true);
+
+    private static class DefaultModuleVersionSelectionReason implements ModuleVersionSelectionReason {
+
+        private final boolean forced;
+        private final boolean conflictResolution;
+
+        private DefaultModuleVersionSelectionReason(boolean forced, boolean conflictResolution) {
+            this.forced = forced;
+            this.conflictResolution = conflictResolution;
+        }
+
+        public boolean isForced() {
+            return forced;
+        }
+
+        public boolean isConflictResolution() {
+            return conflictResolution;
+        }
+
+        //TODO At some point we want to provide information if version was requested in the graph.
+        //Perhaps a method like isRequested(). Not requested means that some particular version was forced but no dependency have requested this version.
+    }
+}
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
index 5d43ac6..4f232aa 100644
--- 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
@@ -15,9 +15,8 @@
  */
 package org.gradle.api.internal.artifacts.mvnsettings;
 
-import jarjar.org.apache.maven.settings.Settings;
-import jarjar.org.apache.maven.settings.building.*;
-
+import org.gradle.mvn3.org.apache.maven.settings.Settings;
+import org.gradle.mvn3.org.apache.maven.settings.building.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,27 +32,30 @@ public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryL
     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;
+    private final MavenSettingsProvider settingsProvider;
 
-    public DefaultLocalMavenRepositoryLocator(MavenFileLocations mavenFileLocations, Map<String, String> systemProperties, Map<String, String> environmentVariables) {
-        this.mavenFileLocations = mavenFileLocations;
+    public DefaultLocalMavenRepositoryLocator(MavenSettingsProvider settingsProvider, Map<String, String> systemProperties,
+                                              Map<String, String> environmentVariables) {
         this.systemProperties = systemProperties;
         this.environmentVariables = environmentVariables;
+        this.settingsProvider = settingsProvider;
     }
 
     public File getLocalMavenRepository() throws CannotLocateLocalMavenRepositoryException{
         try {
-            Settings settings = buildSettings();
+            Settings settings = settingsProvider.buildSettings();
             String repoPath = settings.getLocalRepository();
-            if (repoPath == null) {
-                repoPath = new File(System.getProperty("user.home"), "/.m2/repository").getAbsolutePath();
-                LOGGER.debug(String.format("No local repository in Settings file defined. Using default path: %s", repoPath));
+            if (repoPath != null) {
+                return new File(resolvePlaceholders(repoPath.trim()));
+            } else {
+                File defaultLocation = new File(System.getProperty("user.home"), "/.m2/repository").getAbsoluteFile();
+                LOGGER.debug(String.format("No local repository in Settings file defined. Using default path: %s", defaultLocation));
+                return defaultLocation;
             }
-            return new File(resolvePlaceholders(repoPath.trim()));
         } catch (SettingsBuildingException e) {
-            throw new CannotLocateLocalMavenRepositoryException("Unable to parse local maven settings", e);
+            throw new CannotLocateLocalMavenRepositoryException("Unable to parse local maven settings.", e);
         }
     }
 
@@ -72,17 +74,4 @@ public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryL
 
         return result.toString();
     }
-
-
-    private Settings buildSettings() throws SettingsBuildingException {
-        DefaultSettingsBuilderFactory factory = new DefaultSettingsBuilderFactory();
-        DefaultSettingsBuilder defaultSettingsBuilder = factory.newInstance();
-        DefaultSettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
-        settingsBuildingRequest.setSystemProperties(System.getProperties());
-        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserMavenDir());
-        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserSettingsFile());
-        settingsBuildingRequest.setGlobalSettingsFile(mavenFileLocations.getGlobalSettingsFile());
-        SettingsBuildingResult settingsBuildingResult = defaultSettingsBuilder.build(settingsBuildingRequest);
-        return settingsBuildingResult.getEffectiveSettings();
-    }
 }
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
index 6bde2b9..ff01ffc 100644
--- 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
@@ -38,7 +38,7 @@ public class DefaultMavenFileLocations implements MavenFileLocations {
             if(m2Home==null){
                 return null;
             }
-            DeprecationLogger.nagUserWith("Found defined M2_HOME system property. Handling M2_HOME system property is deprecated in Gradle and will be removed in the next version of Gradle. Please use a M2_HOME environment variable instead.");
+            DeprecationLogger.nagUserOfDeprecated("Found defined M2_HOME system property. Handling M2_HOME system property", "Please use the M2_HOME environment variable instead");
         }
         return new File(m2Home);
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.java
new file mode 100644
index 0000000..6846eb3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenSettingsProvider.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.mvnsettings;
+
+import org.gradle.mvn3.org.apache.maven.settings.Settings;
+import org.gradle.mvn3.org.apache.maven.settings.building.*;
+
+/**
+ * @author Szczepan Faber/Steve Ebersole
+ */
+public class DefaultMavenSettingsProvider implements MavenSettingsProvider {
+
+    private final MavenFileLocations mavenFileLocations;
+
+    public DefaultMavenSettingsProvider(MavenFileLocations mavenFileLocations) {
+        this.mavenFileLocations = mavenFileLocations;
+    }
+
+    public Settings buildSettings() throws SettingsBuildingException {
+        DefaultSettingsBuilderFactory factory = new DefaultSettingsBuilderFactory();
+        DefaultSettingsBuilder defaultSettingsBuilder = factory.newInstance();
+        DefaultSettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
+        settingsBuildingRequest.setSystemProperties(System.getProperties());
+        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserMavenDir());
+        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserSettingsFile());
+        settingsBuildingRequest.setGlobalSettingsFile(mavenFileLocations.getGlobalSettingsFile());
+        SettingsBuildingResult settingsBuildingResult = defaultSettingsBuilder.build(settingsBuildingRequest);
+        return settingsBuildingResult.getEffectiveSettings();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.java
new file mode 100644
index 0000000..dd93102
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenSettingsProvider.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.artifacts.mvnsettings;
+
+import org.gradle.mvn3.org.apache.maven.settings.Settings;
+import org.gradle.mvn3.org.apache.maven.settings.building.SettingsBuildingException;
+
+public interface MavenSettingsProvider {
+    Settings buildSettings() throws SettingsBuildingException;
+}
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
index cca1315..d4c5460 100644
--- 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
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.repositories.AuthenticationSupported;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
 import org.gradle.util.ConfigureUtil;
 
-class AbstractAuthenticationSupportedRepository implements AuthenticationSupported {
+public abstract class AbstractAuthenticationSupportedRepository extends AbstractArtifactRepository implements AuthenticationSupported {
     private final PasswordCredentials passwordCredentials;
 
     AbstractAuthenticationSupportedRepository(PasswordCredentials credentials) {
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
index e1dcddc..46cf19a 100644
--- 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
@@ -15,35 +15,45 @@
  */
 package org.gradle.api.internal.artifacts.repositories;
 
+import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.TransferListener;
 import org.apache.ivy.plugins.resolver.*;
-import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.artifacts.repositories.transport.ProgressLoggingTransferListener;
+import org.gradle.logging.ProgressLoggerFactory;
 
 import java.util.List;
 
 public class CustomResolverArtifactRepository extends FixedResolverArtifactRepository {
-    private final RepositoryTransportFactory repositoryTransportFactory;
+    private final TransferListener transferListener;
+    private final RepositoryCacheManager downloadingCacheManager;
+    private final RepositoryCacheManager localCacheManager;
 
-    public CustomResolverArtifactRepository(DependencyResolver resolver, RepositoryTransportFactory repositoryTransportFactory) {
+    public CustomResolverArtifactRepository(DependencyResolver resolver, ProgressLoggerFactory progressLoggerFactory,
+                                            RepositoryCacheManager localCacheManager, RepositoryCacheManager downloadingCacheManager) {
         super(resolver);
-        this.repositoryTransportFactory = repositoryTransportFactory;
+        this.localCacheManager = localCacheManager;
+        this.downloadingCacheManager = downloadingCacheManager;
+        this.transferListener = new ProgressLoggingTransferListener(progressLoggerFactory, CustomResolverArtifactRepository.class);
         configureResolver(resolver, true);
     }
 
     private void configureResolver(DependencyResolver dependencyResolver, boolean isTopLevel) {
         if (isTopLevel) {
             if (resolver instanceof AbstractResolver && !(resolver instanceof FileSystemResolver)) {
-                ((AbstractResolver) resolver).setRepositoryCacheManager(repositoryTransportFactory.getDownloadingCacheManager());
+                ((AbstractResolver) resolver).setRepositoryCacheManager(downloadingCacheManager);
             }
         }
-        
+
         if (dependencyResolver instanceof FileSystemResolver) {
             ((FileSystemResolver) dependencyResolver).setLocal(true);
-            ((FileSystemResolver) dependencyResolver).setRepositoryCacheManager(repositoryTransportFactory.getLocalCacheManager());
+            ((FileSystemResolver) dependencyResolver).setRepositoryCacheManager(localCacheManager);
         }
         if (dependencyResolver instanceof RepositoryResolver) {
             Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
-            repositoryTransportFactory.attachListener(repository);
+            if (!repository.hasTransferListener(transferListener)) {
+                repository.addTransferListener(transferListener);
+            }
         }
         if (dependencyResolver instanceof DualResolver) {
             DualResolver dualResolver = (DualResolver) dependencyResolver;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
new file mode 100644
index 0000000..b04d8ab
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
@@ -0,0 +1,138 @@
+/*
+ * 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.repositories;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+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.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.*;
+import org.gradle.api.internal.artifacts.ArtifactPublisherFactory;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
+    private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
+    private final FileResolver fileResolver;
+    private final Instantiator instantiator;
+    private final RepositoryTransportFactory transportFactory;
+    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final RepositoryCacheManager localCacheManager;
+    private final RepositoryCacheManager downloadingCacheManager;
+    private final ArtifactPublisherFactory artifactPublisherFactory;
+
+    public DefaultBaseRepositoryFactory(LocalMavenRepositoryLocator localMavenRepositoryLocator,
+                                        FileResolver fileResolver,
+                                        Instantiator instantiator,
+                                        RepositoryTransportFactory transportFactory,
+                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                                        ProgressLoggerFactory progressLoggerFactory,
+                                        RepositoryCacheManager localCacheManager,
+                                        RepositoryCacheManager downloadingCacheManager,
+                                        ArtifactPublisherFactory artifactPublisherFactory) {
+        this.localMavenRepositoryLocator = localMavenRepositoryLocator;
+        this.fileResolver = fileResolver;
+        this.instantiator = instantiator;
+        this.transportFactory = transportFactory;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.localCacheManager = localCacheManager;
+        this.downloadingCacheManager = downloadingCacheManager;
+        this.artifactPublisherFactory = artifactPublisherFactory;
+    }
+
+    public ArtifactRepository createRepository(Object userDescription) {
+        if (userDescription instanceof ArtifactRepository) {
+            return (ArtifactRepository) userDescription;
+        }
+
+        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;
+        }
+
+        DependencyResolver result;
+        if (userDescription instanceof DependencyResolver) {
+            result = (DependencyResolver) userDescription;
+        } else {
+            throw new InvalidUserDataException(String.format("Cannot create a DependencyResolver instance from %s", userDescription));
+        }
+        return new CustomResolverArtifactRepository(result, progressLoggerFactory, localCacheManager, downloadingCacheManager);
+    }
+
+    public FlatDirectoryArtifactRepository createFlatDirRepository() {
+        return instantiator.newInstance(DefaultFlatDirArtifactRepository.class, fileResolver, localCacheManager);
+    }
+
+    public MavenArtifactRepository createMavenLocalRepository() {
+        MavenArtifactRepository mavenRepository = createMavenRepository();
+        final File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
+        mavenRepository.setUrl(localMavenRepository);
+        return mavenRepository;
+    }
+
+    public MavenArtifactRepository createMavenCentralRepository() {
+        MavenArtifactRepository mavenRepository = createMavenRepository();
+        mavenRepository.setUrl(RepositoryHandler.MAVEN_CENTRAL_URL);
+        return mavenRepository;
+    }
+
+    public IvyArtifactRepository createIvyRepository() {
+        return instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
+                locallyAvailableResourceFinder, artifactPublisherFactory
+        );
+    }
+
+    public MavenArtifactRepository createMavenRepository() {
+        return instantiator.newInstance(DefaultMavenArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
+                locallyAvailableResourceFinder
+        );
+    }
+
+    public DependencyResolver toResolver(ArtifactRepository repository) {
+        return ((ArtifactRepositoryInternal) repository).createResolver();
+    }
+
+    public FixedResolverArtifactRepository createResolverBackedRepository(DependencyResolver resolver) {
+        return new FixedResolverArtifactRepository(resolver);
+    }
+
+    private PasswordCredentials createPasswordCredentials() {
+        return instantiator.newInstance(DefaultPasswordCredentials.class);
+    }
+
+}
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
deleted file mode 100644
index e86e296..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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.ExternalResource;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
-import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.api.internal.externalresource.transfer.*;
-import org.gradle.api.internal.file.TemporaryFileProvider;
-import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.hash.HashUtil;
-import org.gradle.util.hash.HashValue;
-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 TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
-
-    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
-    public void put(Artifact artifact, File source, String destination, boolean overwrite) throws IOException {
-        put(source, destination, overwrite);
-        putChecksum("SHA1", source, destination, overwrite);
-    }
-
-    private void putChecksum(String algorithm, File source, String destination, boolean overwrite) throws IOException {
-        File checksumFile = createChecksumFile(source, algorithm);
-        String checksumDestination = destination + "." + algorithm.toLowerCase();
-        put(checksumFile, checksumDestination, overwrite);
-    }
-
-    private File createChecksumFile(File src, String algorithm) {
-        File csFile = temporaryFileProvider.createTemporaryFile("ivytemp", algorithm);
-        HashValue hash = HashUtil.createHash(src, algorithm);
-        GFileUtils.writeFile(hash.asHexString(), csFile);
-        return csFile;
-    }
-
-    @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
index ab320c4..d37651c 100644
--- 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
@@ -16,11 +16,11 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 import com.google.common.collect.Lists;
+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.repositories.FlatDirectoryArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
 import org.gradle.api.internal.file.FileResolver;
 
 import java.io.File;
@@ -29,23 +29,14 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
-public class DefaultFlatDirArtifactRepository implements FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
+public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository implements FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
     private final FileResolver fileResolver;
-    private final RepositoryTransportFactory repositoryTransportFactory;
-    private String name;
     private List<Object> dirs = new ArrayList<Object>();
+    private final RepositoryCacheManager localCacheManager;
 
-    public DefaultFlatDirArtifactRepository(FileResolver fileResolver, RepositoryTransportFactory repositoryTransportFactory) {
+    public DefaultFlatDirArtifactRepository(FileResolver fileResolver, RepositoryCacheManager localCacheManager) {
         this.fileResolver = fileResolver;
-        this.repositoryTransportFactory = repositoryTransportFactory;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
+        this.localCacheManager = localCacheManager;
     }
 
     public Set<File> getDirs() {
@@ -71,14 +62,14 @@ public class DefaultFlatDirArtifactRepository implements FlatDirectoryArtifactRe
         }
 
         FileSystemResolver resolver = new FileSystemResolver();
-        resolver.setName(name);
+        resolver.setName(getName());
         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());
+        resolver.setRepositoryCacheManager(localCacheManager);
         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 5d7d02f..8bf097c 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
@@ -19,13 +19,15 @@ import groovy.lang.Closure;
 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.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.ArtifactPublisherFactory;
 import org.gradle.api.internal.artifacts.repositories.layout.*;
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 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.api.publish.ivy.internal.IvyPublisher;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.WrapUtil;
 
@@ -33,26 +35,25 @@ import java.net.URI;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepository, ArtifactRepositoryInternal {
-    private String name;
+public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepositoryInternal {
     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;
+    private final ArtifactPublisherFactory artifactPublisherFactory;
 
     public DefaultIvyArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
                                         LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
-                                        CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+                                        ArtifactPublisherFactory artifactPublisherFactory) {
         super(credentials);
         this.fileResolver = fileResolver;
         this.transportFactory = transportFactory;
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
-        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
         this.additionalPatternsLayout = new AdditionalPatternsRepositoryLayout(fileResolver);
         this.layout = new GradleRepositoryLayout();
+        this.artifactPublisherFactory = artifactPublisherFactory;
     }
 
     public DependencyResolver createResolver() {
@@ -78,22 +79,14 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
             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);
+            return new IvyResolver(getName(), transportFactory.createHttpTransport(getName(), getCredentials()), locallyAvailableResourceFinder);
         }
         if (WrapUtil.toSet("file").containsAll(schemes)) {
-            return new IvyResolver(name, transportFactory.createFileTransport(name), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+            return new IvyResolver(getName(), transportFactory.createFileTransport(getName()), locallyAvailableResourceFinder);
         }
         throw new InvalidUserDataException("You cannot mix file and http(s) urls for a single ivy repository. Please declare 2 separate repositories.");
     }
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
     public URI getUrl() {
         return baseUrl == null ? null : fileResolver.resolveUri(baseUrl);
     }
@@ -161,4 +154,9 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         }
     }
 
+    public IvyPublisher createPublisher() {
+        return new IvyPublisher(artifactPublisherFactory.createArtifactPublisher(this));
+    }
+
+
 }
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
index 8d15ea2..a57152c 100644
--- 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
@@ -21,9 +21,9 @@ 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.resolver.MavenResolver;
 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;
 
@@ -36,28 +36,16 @@ 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) {
+                                          LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder) {
         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() {
@@ -90,7 +78,7 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
             throw new InvalidUserDataException("You must specify a URL for a Maven repository.");
         }
 
-        MavenResolver resolver = new MavenResolver(name, rootUri, getTransport(rootUri.getScheme()), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        MavenResolver resolver = new MavenResolver(getName(), rootUri, getTransport(rootUri.getScheme()), locallyAvailableResourceFinder);
         for (URI repoUrl : getArtifactUrls()) {
             resolver.addArtifactLocation(repoUrl, null);
         }
@@ -99,9 +87,9 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
 
     private RepositoryTransport getTransport(String scheme) {
         if (scheme.equalsIgnoreCase("file")) {
-            return transportFactory.createFileTransport(name);
+            return transportFactory.createFileTransport(getName());
         } else {
-            return transportFactory.createHttpTransport(name, getCredentials());
+            return transportFactory.createHttpTransport(getName(), getCredentials());
         }
     }
 
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
deleted file mode 100644
index 378958a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
+++ /dev/null
@@ -1,117 +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.repositories;
-
-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.dsl.RepositoryHandler;
-import org.gradle.api.artifacts.repositories.*;
-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.internal.reflect.Instantiator;
-import org.gradle.util.ConfigureUtil;
-
-import java.io.File;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolverFactory implements ResolverFactory {
-    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 ArtifactRepository createRepository(Object userDescription) {
-        if (userDescription instanceof ArtifactRepository) {
-            return (ArtifactRepository) userDescription;
-        }
-
-        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;
-        }
-
-        DependencyResolver result;
-        if (userDescription instanceof DependencyResolver) {
-            result = (DependencyResolver) userDescription;
-        } else {
-            throw new InvalidUserDataException(String.format("Cannot create a DependencyResolver instance from %s", userDescription));
-        }
-        return new CustomResolverArtifactRepository(result, transportFactory);
-    }
-
-    public FlatDirectoryArtifactRepository createFlatDirRepository() {
-        return instantiator.newInstance(DefaultFlatDirArtifactRepository.class, fileResolver, transportFactory);
-    }
-
-    public MavenArtifactRepository createMavenLocalRepository() {
-        MavenArtifactRepository mavenRepository = createMavenRepository();
-        final File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
-        mavenRepository.setUrl(localMavenRepository);
-        return mavenRepository;
-    }
-
-    public MavenArtifactRepository createMavenCentralRepository() {
-        MavenArtifactRepository mavenRepository = createMavenRepository();
-        mavenRepository.setUrl(RepositoryHandler.MAVEN_CENTRAL_URL);
-        return mavenRepository;
-    }
-
-    public IvyArtifactRepository createIvyRepository() {
-        return instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
-                locallyAvailableResourceFinder, cachedExternalResourceIndex
-        );
-    }
-
-    public MavenArtifactRepository createMavenRepository() {
-        return instantiator.newInstance(DefaultMavenArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
-                locallyAvailableResourceFinder, cachedExternalResourceIndex
-        );
-    }
-
-    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
deleted file mode 100644
index 92c2d83..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/EnhancedArtifactDownloadReport.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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
deleted file mode 100644
index e39ad35..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceRepository.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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
deleted file mode 100644
index 3481dbb..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 (!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/IvyArtifactRepositoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyArtifactRepositoryInternal.java
new file mode 100644
index 0000000..d34b523
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyArtifactRepositoryInternal.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.repositories;
+
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.publish.ivy.internal.IvyPublisher;
+
+public interface IvyArtifactRepositoryInternal extends IvyArtifactRepository, ArtifactRepositoryInternal {
+
+    IvyPublisher createPublisher();
+
+}
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
deleted file mode 100644
index 18742ea..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyResolver.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.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
deleted file mode 100644
index 4912aad..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/MavenResolver.java
+++ /dev/null
@@ -1,316 +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.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
deleted file mode 100644
index ba67378..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PatternBasedResolver.java
+++ /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.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
deleted file mode 100644
index 84f7612..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ProgressLoggingTransferListener.java
+++ /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.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/DownloadingRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
index 21b77ae..40cdadc 100644
--- 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
@@ -15,7 +15,6 @@
  */
 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;
@@ -28,16 +27,20 @@ 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.Resource;
 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.CacheLockingManager;
 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.file.TemporaryFileProvider;
 import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.io.IOException;
@@ -49,14 +52,19 @@ import java.text.ParseException;
 public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheManager {
     private final FileStore<ArtifactRevisionId> fileStore;
     private final CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final CacheLockingManager cacheLockingManager;
 
-    public DownloadingRepositoryCacheManager(String name, FileStore<ArtifactRevisionId> fileStore, CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex) {
+    public DownloadingRepositoryCacheManager(String name, FileStore<ArtifactRevisionId> fileStore, CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex,
+                                             TemporaryFileProvider temporaryFileProvider, CacheLockingManager cacheLockingManager) {
         super(name);
         this.fileStore = fileStore;
         this.artifactUrlCachedResolutionIndex = artifactUrlCachedResolutionIndex;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cacheLockingManager = cacheLockingManager;
     }
 
-    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
+    public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
                                            ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
         EnhancedArtifactDownloadReport adr = new EnhancedArtifactDownloadReport(artifact);
 
@@ -69,7 +77,7 @@ public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheMa
         try {
             ResolvedResource artifactRef = resourceResolver.resolve(artifact);
             if (artifactRef != null) {
-                ArtifactOrigin origin = new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
+                ArtifactOriginWithMetaData origin = new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
                 if (listener != null) {
                     listener.startArtifactDownload(this, artifactRef, artifact, origin);
                 }
@@ -96,19 +104,26 @@ public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheMa
         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);
+    private File downloadArtifactFile(final Artifact artifact, final ResourceDownloader resourceDownloader, final ResolvedResource artifactRef) throws IOException {
+        final Resource resource = artifactRef.getResource();
+        final File tmpFile = temporaryFileProvider.createTemporaryFile("gradle_download", "bin");
+        try {
+            resourceDownloader.download(artifact, resource, tmpFile);
+            return cacheLockingManager.useCache(String.format("Store %s", artifact), new Factory<File>() {
+                public File create() {
+                    FileStoreEntry fileStoreEntry = fileStore.move(artifact.getId(), tmpFile);
+                    File fileInFileStore = fileStoreEntry.getFile();
+                    if (resource instanceof ExternalResource) {
+                        ExternalResource externalResource = (ExternalResource) resource;
+                        ExternalResourceMetaData metaData = externalResource.getMetaData();
+                        artifactUrlCachedResolutionIndex.store(metaData.getLocation(), fileInFileStore, metaData);
+                    }
+                    return fileInFileStore;
+                }
+            });
+        } finally {
+            tmpFile.delete();
         }
-
-        return fileInFileStore;
     }
 
     public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, final ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.java
new file mode 100644
index 0000000..6c21c03
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/EnhancedArtifactDownloadReport.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.artifacts.repositories.cachemanager;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+
+public class EnhancedArtifactDownloadReport extends ArtifactDownloadReport {
+    private Throwable failure;
+    private ArtifactOriginWithMetaData artifactOrigin;
+
+    public EnhancedArtifactDownloadReport(Artifact artifact) {
+        super(artifact);
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+
+    @Override
+    public ArtifactOriginWithMetaData getArtifactOrigin() {
+        return artifactOrigin;
+    }
+
+    @Override
+    public void setArtifactOrigin(ArtifactOrigin origin) {
+        if (origin instanceof ArtifactOriginWithMetaData) {
+            artifactOrigin = (ArtifactOriginWithMetaData) origin;
+            super.setArtifactOrigin(origin);
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    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/cachemanager/LocalFileRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
index 9d01732..ca0fe6c 100644
--- 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
@@ -29,6 +29,8 @@ 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.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
 
 import java.io.File;
 import java.text.ParseException;
@@ -42,9 +44,9 @@ public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheMana
         super(name);
     }
 
-    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+    public EnhancedArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
         long start = System.currentTimeMillis();
-        ArtifactDownloadReport report = new ArtifactDownloadReport(artifact);
+        EnhancedArtifactDownloadReport report = new EnhancedArtifactDownloadReport(artifact);
         ResolvedResource resolvedResource = resourceResolver.resolve(artifact);
         if (resolvedResource == null) {
             report.setDownloadStatus(DownloadStatus.FAILED);
@@ -56,7 +58,7 @@ public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheMana
         File file = new File(resolvedResource.getResource().getName());
         assert file.isFile();
 
-        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
+        ArtifactOrigin origin = new ArtifactOriginWithMetaData(artifact, true, new DefaultExternalResourceMetaData(file.getAbsolutePath()));
         report.setDownloadStatus(DownloadStatus.NO);
         report.setArtifactOrigin(origin);
         report.setSize(file.length());
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
index b76f88e..2555bf5 100644
--- 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
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.artifacts.repositories.layout;
 
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 
 import java.net.URI;
 
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
index ac56dd1..ae57ac4 100644
--- 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
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.artifacts.repositories.layout;
 
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 
 import java.net.URI;
 
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
index 65d1ede..1279f36 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.repositories.layout;
 
-import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 
 import java.net.URI;
 import java.util.LinkedHashSet;
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
index fdaf3b7..451db67 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.repositories.layout;
 
-import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
 
 import java.net.URI;
 import java.util.Set;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/AbstractVersionList.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/AbstractVersionList.java
new file mode 100644
index 0000000..695ecbe
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/AbstractVersionList.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.artifacts.repositories.resolver;
+
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+abstract class AbstractVersionList implements VersionList {
+    public boolean isEmpty() {
+        return getVersionStrings().isEmpty();
+    }
+
+    public List<String> sortLatestFirst(LatestStrategy latestStrategy) {
+        List<String> versions = new ArrayList<String>(getVersionStrings());
+        ArtifactInfo[] artifactInfos = new ArtifactInfo[versions.size()];
+        for (int i = 0; i < versions.size(); i++) {
+            String version = versions.get(i);
+            artifactInfos[i] = new VersionArtifactInfo(version);
+        }
+        List<ArtifactInfo> sorted = latestStrategy.sort(artifactInfos);
+        Collections.reverse(sorted);
+
+        List<String> sortedVersions = new ArrayList<String>();
+        for (ArtifactInfo info : sorted) {
+            sortedVersions.add(info.getRevision());
+        }
+        return sortedVersions;
+    }
+
+    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/resolver/ChainedVersionLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ChainedVersionLister.java
new file mode 100644
index 0000000..fb1657c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ChainedVersionLister.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.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+import org.gradle.util.DeprecationLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+public class ChainedVersionLister implements VersionLister {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceResolver.class);
+    private final List<VersionLister> versionListers;
+
+    public ChainedVersionLister(VersionLister... versionlisters) {
+        this.versionListers = Arrays.asList(versionlisters);
+    }
+
+    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId)  {
+        final List<VersionList> versionLists = new ArrayList<VersionList>();
+        for (VersionLister lister : versionListers) {
+            versionLists.add(lister.getVersionList(moduleRevisionId));
+        }
+        return new AbstractVersionList() {
+            public void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
+                final Iterator<VersionList> versionListIterator = versionLists.iterator();
+                while (versionListIterator.hasNext()) {
+                    VersionList list = versionListIterator.next();
+                    try {
+                        list.visit(pattern, artifact);
+                        return;
+                    } catch (ResourceNotFoundException e) {
+                        if (!versionListIterator.hasNext()) {
+                            throw e;
+                        }
+                    } catch (Exception e) {
+                        if (versionListIterator.hasNext()) {
+                            String deprecationMessage = String.format(
+                                    "Error listing versions of %s using %s. Will attempt an alternate way to list versions",
+                                    moduleRevisionId, list.getClass()
+                            );
+                            DeprecationLogger.nagUserOfDeprecatedBehaviour(deprecationMessage);
+                            LOGGER.debug(deprecationMessage, e);
+                        } else {
+                            throw new ResourceException(String.format("Failed to list versions for %s.", moduleRevisionId), e);
+                        }
+                    }
+                }
+            }
+
+            public Set<String> getVersionStrings() {
+                Set<String> allVersions = new HashSet<String>();
+                for (VersionList versionList : versionLists) {
+                    allVersions.addAll(versionList.getVersionStrings());
+                }
+                return allVersions;
+            }
+        };
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/DefaultVersionList.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/DefaultVersionList.java
new file mode 100644
index 0000000..9eafb22
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/DefaultVersionList.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.repositories.resolver;
+
+import com.google.common.collect.Iterables;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultVersionList extends AbstractVersionList {
+    private final Set<String> versions = new HashSet<String>();
+
+    public DefaultVersionList(List<String> versionStrings) {
+        this.versions.addAll(versionStrings);
+    }
+
+    public DefaultVersionList() {
+    }
+
+    protected void add(Iterable<String> versionStrings) {
+        Iterables.addAll(versions, versionStrings);
+    }
+
+    public void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Set<String> getVersionStrings() {
+        return versions;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
new file mode 100644
index 0000000..66ca797
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolver;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleId;
+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.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.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+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.ResourceMDParser;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.Message;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
+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.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.*;
+
+public class ExternalResourceResolver extends BasicResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceResolver.class);
+
+    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;
+    protected VersionLister versionLister;
+    private ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
+        public ResolvedResource resolve(Artifact artifact) {
+            return getArtifactRef(toSystem(artifact), null);
+        }
+    };
+    private final ResourceDownloader resourceDownloader = new ResourceDownloader() {
+        public void download(Artifact artifact, Resource resource, File dest) throws IOException {
+            getAndCheck(resource, dest);
+        }
+    };
+
+    public ExternalResourceResolver(String name,
+                                    ExternalResourceRepository repository,
+                                    VersionLister versionLister,
+                                    LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder
+    ) {
+        setName(name);
+        this.versionLister = versionLister;
+        this.repository = repository;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+    }
+
+    protected ExternalResourceRepository getRepository() {
+        return repository;
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data)
+            throws ParseException {
+        DependencyDescriptor systemDd = dd;
+        DependencyDescriptor nsDd = fromSystem(dd);
+
+        ModuleRevisionId systemMrid = systemDd.getDependencyRevisionId();
+        ModuleRevisionId nsMrid = nsDd.getDependencyRevisionId();
+
+        boolean isDynamic = getSettings().getVersionMatcher().isDynamic(systemMrid);
+        ResolvedModuleRevision rmr = null;
+
+        ResolvedResource ivyRef = findIvyFileRef(nsDd, data);
+
+        // get module descriptor
+        ModuleDescriptor nsMd;
+        ModuleDescriptor systemMd;
+        if (ivyRef == null) {
+            if (!isAllownomd()) {
+                LOGGER.debug("No ivy file found for {}", systemMrid);
+                return null;
+            }
+            nsMd = DefaultModuleDescriptor.newDefaultInstance(nsMrid, nsDd
+                    .getAllDependencyArtifacts());
+            ResolvedResource artifactRef = findFirstArtifactRef(nsMd, nsDd, data);
+            if (artifactRef == null) {
+                LOGGER.debug("No ivy file nor artifact found for {}", systemMrid);
+                return null;
+            } else {
+                long lastModified = artifactRef.getLastModified();
+                if (lastModified != 0 && nsMd instanceof DefaultModuleDescriptor) {
+                    ((DefaultModuleDescriptor) nsMd).setLastModified(lastModified);
+                }
+                Message.verbose("\t" + getName() + ": no ivy file found for " + systemMrid
+                        + ": using default data");
+                if (isDynamic) {
+                    nsMd.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(nsMrid,
+                            artifactRef.getRevision()));
+                }
+                systemMd = toSystem(nsMd);
+                MetadataArtifactDownloadReport madr =
+                        new MetadataArtifactDownloadReport(systemMd.getMetadataArtifact());
+                madr.setDownloadStatus(DownloadStatus.NO);
+                madr.setSearched(true);
+                rmr = new ResolvedModuleRevision(this, this, systemMd, madr, isForce());
+            }
+        } else {
+            if (ivyRef instanceof MDResolvedResource) {
+                rmr = ((MDResolvedResource) ivyRef).getResolvedModuleRevision();
+            }
+            if (rmr == null) {
+                rmr = parse(ivyRef, systemDd, data);
+            }
+            if (!rmr.getReport().isDownloaded()
+                    && rmr.getReport().getLocalFile() != null) {
+                return rmr;
+            } else {
+                nsMd = rmr.getDescriptor();
+
+                // check descriptor data is in sync with resource revision and names
+                systemMd = toSystem(nsMd);
+                if (isCheckconsistency()) {
+                    checkDescriptorConsistency(systemMrid, systemMd, ivyRef);
+                    checkDescriptorConsistency(nsMrid, nsMd, ivyRef);
+                }
+                rmr = new ResolvedModuleRevision(
+                        this, this, systemMd, toSystem(rmr.getReport()), isForce());
+            }
+        }
+
+        return rmr;
+    }
+
+    public ResolvedModuleRevision parse(final ResolvedResource mdRef, DependencyDescriptor dd,
+            ResolveData data) throws ParseException {
+
+        DependencyDescriptor nsDd = dd;
+        dd = toSystem(nsDd);
+
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        ModuleDescriptorParser parser = ModuleDescriptorParserRegistry
+                .getInstance().getParser(mdRef.getResource());
+        if (parser == null) {
+            throw new RuntimeException("no module descriptor parser available for " + mdRef.getResource());
+        }
+
+        ModuleRevisionId resolvedMrid = mrid;
+
+        // first check if this dependency has not yet been resolved
+        if (getSettings().getVersionMatcher().isDynamic(mrid)) {
+            resolvedMrid = ModuleRevisionId.newInstance(mrid, mdRef.getRevision());
+        }
+
+        Artifact moduleArtifact = parser.getMetadataArtifact(resolvedMrid, mdRef.getResource());
+        return getRepositoryCacheManager().cacheModuleDescriptor(this, mdRef, dd, moduleArtifact, resourceDownloader, getCacheOptions(data));
+    }
+
+    private void checkDescriptorConsistency(ModuleRevisionId mrid, ModuleDescriptor md,
+            ResolvedResource ivyRef) throws ParseException {
+        boolean ok = true;
+        StringBuilder errors = new StringBuilder();
+        if (!mrid.getOrganisation().equals(md.getModuleRevisionId().getOrganisation())) {
+            Message.error("\t" + getName() + ": bad organisation found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getOrganisation() + "' found='"
+                    + md.getModuleRevisionId().getOrganisation() + "'");
+            errors.append("bad organisation: expected='" + mrid.getOrganisation() + "' found='"
+                    + md.getModuleRevisionId().getOrganisation() + "'; ");
+            ok = false;
+        }
+        if (!mrid.getName().equals(md.getModuleRevisionId().getName())) {
+            Message.error("\t" + getName() + ": bad module name found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getName() + " found='"
+                    + md.getModuleRevisionId().getName() + "'");
+            errors.append("bad module name: expected='" + mrid.getName() + "' found='"
+                    + md.getModuleRevisionId().getName() + "'; ");
+            ok = false;
+        }
+        if (mrid.getBranch() != null
+                && !mrid.getBranch().equals(md.getModuleRevisionId().getBranch())) {
+            Message.error("\t" + getName() + ": bad branch name found in " + ivyRef.getResource()
+                    + ": expected='" + mrid.getBranch() + " found='"
+                    + md.getModuleRevisionId().getBranch() + "'");
+            errors.append("bad branch name: expected='" + mrid.getBranch() + "' found='"
+                    + md.getModuleRevisionId().getBranch() + "'; ");
+            ok = false;
+        }
+        if (ivyRef.getRevision() != null && !ivyRef.getRevision().startsWith("working@")) {
+            ModuleRevisionId expectedMrid = ModuleRevisionId
+                    .newInstance(mrid, ivyRef.getRevision());
+            if (!getSettings().getVersionMatcher().accept(expectedMrid, md)) {
+                Message.error("\t" + getName() + ": bad revision found in " + ivyRef.getResource()
+                        + ": expected='" + ivyRef.getRevision() + " found='"
+                        + md.getModuleRevisionId().getRevision() + "'");
+                errors.append("bad revision: expected='" + ivyRef.getRevision() + "' found='"
+                        + md.getModuleRevisionId().getRevision() + "'; ");
+                ok = false;
+            }
+        }
+        if (!getSettings().getStatusManager().isStatus(md.getStatus())) {
+            Message.error("\t" + getName() + ": bad status found in " + ivyRef.getResource()
+                    + ": '" + md.getStatus() + "'");
+            errors.append("bad status: '" + md.getStatus() + "'; ");
+            ok = false;
+        }
+        for (Iterator it = mrid.getExtraAttributes().entrySet().iterator(); it.hasNext();) {
+            Map.Entry extra = (Map.Entry) it.next();
+            if (extra.getValue() != null && !extra.getValue().equals(
+                                                md.getExtraAttribute((String) extra.getKey()))) {
+                String errorMsg = "bad " + extra.getKey() + " found in " + ivyRef.getResource()
+                                        + ": expected='" + extra.getValue() + "' found='"
+                                        + md.getExtraAttribute((String) extra.getKey()) + "'";
+                Message.error("\t" + getName() + ": " + errorMsg);
+                errors.append(errorMsg + ";");
+                ok = false;
+            }
+        }
+        if (!ok) {
+            throw new ParseException("inconsistent module descriptor file found in '"
+                    + ivyRef.getResource() + "': " + errors, 0);
+        }
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        return findResourceUsingPatterns(mrid, ivyPatterns, DefaultArtifact.newIvyArtifact(mrid, data.getDate()), getRMDParser(dd, data), data.getDate(), true);
+    }
+
+    @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) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @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();
+        return findResourceUsingPatterns(mrid, artifactPatterns, artifact,
+                getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
+    }
+
+    protected ResourceMDParser getDefaultRMDParser(final ModuleId mid) {
+        return new ResourceMDParser() {
+            public MDResolvedResource parse(Resource resource, String rev) {
+                DefaultModuleDescriptor md =
+                    DefaultModuleDescriptor.newDefaultInstance(new ModuleRevisionId(mid, rev));
+                md.setStatus("integration");
+                MetadataArtifactDownloadReport madr =
+                    new MetadataArtifactDownloadReport(md.getMetadataArtifact());
+                madr.setDownloadStatus(DownloadStatus.NO);
+                madr.setSearched(true);
+                return new MDResolvedResource(resource, rev, new ResolvedModuleRevision(ExternalResourceResolver.this, ExternalResourceResolver.this, md, madr, isForce()));
+            }
+        };
+    }
+
+    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) {
+            ResourcePattern resourcePattern = toResourcePattern(pattern);
+            ResolvedResource rres = findResourceUsingPattern(moduleRevision, resourcePattern, 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, VersionList versions, ResourceMDParser rmdparser, Date date, ResourcePattern pattern, Artifact artifact, boolean forDownload) {
+        String name = getName();
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        List<String> sorted = versions.sortLatestFirst(getLatestStrategy());
+        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);
+            artifact = DefaultArtifact.cloneWithAnotherMrid(artifact, foundMrid);
+            String resourcePath = pattern.toPath(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 (!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;
+    }
+
+    protected ResolvedResource findResourceUsingPattern(ModuleRevisionId moduleRevisionId, ResourcePattern pattern, Artifact artifact, ResourceMDParser resourceParser, Date date, boolean forDownload) {
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        if (!versionMatcher.isDynamic(moduleRevisionId)) {
+            return findStaticResourceUsingPattern(moduleRevisionId, pattern, artifact, forDownload);
+        } else {
+            return findDynamicResourceUsingPattern(resourceParser, moduleRevisionId, pattern, artifact, date, forDownload);
+        }
+    }
+
+    private ResolvedResource findStaticResourceUsingPattern(ModuleRevisionId moduleRevisionId, ResourcePattern pattern, Artifact artifact, boolean forDownload) {
+        String resourceName = pattern.toPath(artifact);
+        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, ResourcePattern pattern, Artifact artifact, Date date, boolean forDownload) {
+        VersionList versions = listVersions(moduleRevisionId, pattern, artifact);
+        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);
+            }
+        }
+    }
+
+    public EnhancedArtifactDownloadReport download(Artifact artifact) {
+        RepositoryCacheManager cacheManager = getRepositoryCacheManager();
+        return (EnhancedArtifactDownloadReport) cacheManager.download(artifact, artifactResourceResolver, resourceDownloader, new CacheDownloadOptions());
+    }
+
+    @Override
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void reportFailure() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void reportFailure(Artifact art) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public OrganisationEntry[] listOrganisations() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RevisionEntry[] listRevisions(ModuleEntry mod) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    protected Resource getResource(String source) throws IOException {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    protected Resource getResource(String source, Artifact target, boolean forDownload) {
+        try {
+            if (forDownload) {
+                ArtifactRevisionId arid = target.getId();
+                LocallyAvailableResourceCandidates localCandidates = locallyAvailableResourceFinder.findCandidates(arid);
+                ExternalResource resource = repository.getResource(source, localCandidates);
+                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);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not get resource '%s'.", source), e);
+        }
+    }
+
+    protected VersionList listVersions(ModuleRevisionId moduleRevisionId, ResourcePattern pattern, Artifact artifact) {
+        try {
+            VersionList versionList = versionLister.getVersionList(moduleRevisionId);
+            versionList.visit(pattern, artifact);
+            return versionList;
+        } catch (ResourceNotFoundException e) {
+            LOGGER.debug(String.format("Unable to load version list for %s from %s", moduleRevisionId.getModuleId(), getRepository()));
+            return new DefaultVersionList(Collections.<String>emptyList());
+        }
+    }
+
+    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");
+        }
+
+        ExternalResource externalResource = (ExternalResource) resource;
+        try {
+            externalResource.writeTo(destination);
+        } finally {
+            externalResource.close();
+        }
+        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");
+        }
+        String destination = toResourcePattern(destinationPattern).toPath(artifact);
+
+        put(src, destination);
+        LOGGER.info("Published {} to {}", artifact.getName(), hidePassword(destination));
+    }
+
+    private void put(File src, String destination) throws IOException {
+        String[] checksums = getChecksumAlgorithms();
+        if (checksums.length != 0) {
+            // Should not be reachable for publishing
+            throw new UnsupportedOperationException();
+        }
+
+        repository.put(src, destination);
+    }
+
+    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<String> patterns) {
+        ivyPatterns = patterns;
+    }
+
+    protected void setArtifactPatterns(List<String> 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 ResourcePattern toResourcePattern(String pattern) {
+        return isM2compatible() ? new M2ResourcePattern(pattern) : new IvyResourcePattern(pattern);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.java
new file mode 100644
index 0000000..e5fb66b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResolver.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.resolver;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+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
+    ) {
+        super(name, transport.getRepository(), new ResourceVersionLister(transport.getRepository()), locallyAvailableResourceFinder);
+        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/resolver/IvyResourcePattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResourcePattern.java
new file mode 100644
index 0000000..3e04de4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResourcePattern.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.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class IvyResourcePattern implements ResourcePattern {
+    private final String pattern;
+
+    public IvyResourcePattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Ivy pattern '%s'", pattern);
+    }
+
+    public String toPath(Artifact artifact) {
+        Map<String, Object> attributes = toAttributes(artifact);
+        return IvyPatternHelper.substituteTokens(pattern, attributes);
+    }
+
+    public String toPathWithoutRevision(Artifact artifact) {
+        Map<String, Object> attributes = toAttributes(artifact);
+        attributes.remove(IvyPatternHelper.REVISION_KEY);
+        return IvyPatternHelper.substituteTokens(pattern, attributes);
+    }
+
+    public String toModulePath(Artifact artifact) {
+        throw new UnsupportedOperationException("not implemented yet.");
+    }
+
+    public String toModuleVersionPath(Artifact artifact) {
+        throw new UnsupportedOperationException("not implemented yet.");
+    }
+
+    protected Map<String, Object> toAttributes(Artifact artifact) {
+        return new HashMap<String, Object>(artifact.getAttributes());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.java
new file mode 100644
index 0000000..dd53162
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePattern.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.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.util.Map;
+
+public class M2ResourcePattern extends IvyResourcePattern {
+    public M2ResourcePattern(String pattern) {
+        super(pattern);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("M2 pattern '%s'", getPattern());
+    }
+
+    @Override
+    public String toModulePath(Artifact artifact) {
+        String pattern = getPattern();
+        if (!pattern.endsWith(MavenPattern.M2_PATTERN)) {
+            throw new UnsupportedOperationException("Cannot locate module for non-maven layout.");
+        }
+        String metaDataPattern = pattern.substring(0, pattern.length() - MavenPattern.M2_PER_MODULE_PATTERN.length() - 1);
+        return IvyPatternHelper.substituteTokens(metaDataPattern, toAttributes(artifact));
+    }
+
+    @Override
+    public String toModuleVersionPath(Artifact artifact) {
+        String pattern = getPattern();
+        if (!pattern.endsWith(MavenPattern.M2_PATTERN)) {
+            throw new UnsupportedOperationException("Cannot locate module version for non-maven layout.");
+        }
+        String metaDataPattern = pattern.substring(0, pattern.length() - MavenPattern.M2_PER_MODULE_VERSION_PATTERN.length() - 1);
+        return IvyPatternHelper.substituteTokens(metaDataPattern, toAttributes(artifact));
+    }
+
+    @Override
+    protected Map<String, Object> toAttributes(Artifact artifact) {
+        Map<String, Object> attributes = super.toAttributes(artifact);
+        String org = (String) attributes.get(IvyPatternHelper.ORGANISATION_KEY);
+        if (org != null) {
+            attributes.put(IvyPatternHelper.ORGANISATION_KEY, org.replace(".", "/"));
+        }
+        return attributes;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadata.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadata.java
new file mode 100644
index 0000000..08ee788
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadata.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.repositories.resolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class MavenMetadata {
+    String timestamp;
+    String buildNumber;
+    List<String> versions = new ArrayList<String>();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java
new file mode 100644
index 0000000..94fa481
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenMetadataLoader.java
@@ -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.api.internal.artifacts.repositories.resolver;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.ContextualSAXHandler;
+import org.apache.ivy.util.XMLHelper;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+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;
+
+class MavenMetadataLoader {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MavenMetadataLoader.class);
+
+    private final ExternalResourceRepository repository;
+
+    public MavenMetadataLoader(ExternalResourceRepository repository) {
+        this.repository = repository;
+    }
+
+    public MavenMetadata load(String metadataLocation) throws ResourceNotFoundException, ResourceException {
+        MavenMetadata metadata = new MavenMetadata();
+        try {
+            parseMavenMetadataInfo(metadataLocation, metadata);
+        } catch (ResourceException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ResourceException(String.format("Unable to load Maven meta-data from %s.", metadataLocation), e);
+        }
+        return metadata;
+    }
+
+    private void parseMavenMetadataInfo(final String metadataLocation, final MavenMetadata metadata) throws Exception {
+        final ExternalResource resource = repository.getResource(metadataLocation);
+        if (resource == null) {
+            throw new ResourceNotFoundException(String.format("Maven meta-data not available: %s", metadataLocation));
+        }
+        try {
+            parseMavenMetadataInto(resource, metadata);
+        } finally {
+            resource.close();
+        }
+    }
+
+    private void parseMavenMetadataInto(Resource metadataResource, final MavenMetadata mavenMetadata) throws IOException, SAXException, ParserConfigurationException {
+        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);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenPattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenPattern.java
new file mode 100644
index 0000000..783adf9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenPattern.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.artifacts.repositories.resolver;
+
+public class MavenPattern {
+    public static final String M2_PER_MODULE_VERSION_PATTERN = "[artifact]-[revision](-[classifier]).[ext]";
+    public static final String M2_PER_MODULE_PATTERN = "[revision]/" + M2_PER_MODULE_VERSION_PATTERN;
+    public static final String M2_PATTERN = "[organisation]/[module]/" + M2_PER_MODULE_PATTERN;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
new file mode 100644
index 0000000..0b774a8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolver;
+
+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.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.util.Message;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+import org.gradle.api.resources.ResourceException;
+import org.gradle.util.DeprecationLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+public class MavenResolver extends ExternalResourceResolver implements PatternBasedResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MavenResolver.class);
+    private final RepositoryTransport transport;
+    private final String root;
+    private final List<String> artifactRoots = new ArrayList<String>();
+    private String pattern = MavenPattern.M2_PATTERN;
+    private boolean usepoms = true;
+    private boolean useMavenMetadata = true;
+    private final MavenMetadataLoader mavenMetaDataLoader;
+
+    public MavenResolver(String name, URI rootUri, RepositoryTransport transport,
+                         LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder) {
+        super(name,
+                transport.getRepository(),
+                new ChainedVersionLister(new MavenVersionLister(transport.getRepository()), new ResourceVersionLister(transport.getRepository())),
+                locallyAvailableResourceFinder);
+        transport.configureCacheManager(this);
+
+        this.mavenMetaDataLoader = new MavenMetadataLoader(transport.getRepository());
+        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 (isUsepoms()) {
+            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 (isUsepoms()) {
+            ModuleRevisionId moduleRevisionId = 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());
+            ResourcePattern resourcePattern = toResourcePattern(pattern);
+            return findResourceUsingPattern(moduleRevisionId, resourcePattern, pomArtifact, getRMDParser(dd, data), data.getDate(), forDownload);
+        }
+        return null;
+    }
+
+    protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
+        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+
+        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);
+            ResourcePattern resourcePattern = toResourcePattern(pattern);
+            return findResourceUsingPattern(moduleRevisionId, resourcePattern, artifact, getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
+        }
+        return null;
+    }
+
+    private String findUniqueSnapshotVersion(ModuleRevisionId moduleRevisionId) {
+        Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, new Date());
+        String metadataLocation = toResourcePattern(getWholePattern()).toModuleVersionPath(pomArtifact) + "/maven-metadata.xml";
+        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;
+    }
+
+    private MavenMetadata parseMavenMetadata(String metadataLocation) {
+        if (shouldUseMavenMetadata(pattern)) {
+            try {
+                return mavenMetaDataLoader.load(metadataLocation);
+            } catch (ResourceNotFoundException e) {
+                return new MavenMetadata();
+            } catch (ResourceException e) {
+                LOGGER.warn("impossible to access maven metadata file, ignored.", e);
+            }
+        }
+        return new MavenMetadata();
+    }
+
+    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();
+    }
+
+    public boolean isUseMavenMetadata() {
+        return useMavenMetadata;
+    }
+
+    @Deprecated
+    public void setUseMavenMetadata(boolean useMavenMetadata) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("MavenResolver.setUseMavenMetadata(boolean)");
+        this.useMavenMetadata = useMavenMetadata;
+        if (useMavenMetadata) {
+            this.versionLister = new ChainedVersionLister(
+                    new MavenVersionLister(getRepository()),
+                    new ResourceVersionLister(getRepository()));
+        } else {
+            this.versionLister = new ResourceVersionLister(getRepository());
+        }
+    }
+
+    private boolean shouldUseMavenMetadata(String pattern) {
+        return isUseMavenMetadata() && pattern.endsWith(MavenPattern.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.");
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionLister.java
new file mode 100644
index 0000000..2609b47
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionLister.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.repositories.resolver;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MavenVersionLister implements VersionLister {
+    private final MavenMetadataLoader mavenMetadataLoader;
+
+    public MavenVersionLister(ExternalResourceRepository repository) {
+        this.mavenMetadataLoader = new MavenMetadataLoader(repository);
+    }
+
+    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId) {
+        return new DefaultVersionList() {
+            final Set<String> searched = new HashSet<String>();
+
+            public void visit(ResourcePattern resourcePattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
+                String metadataLocation = resourcePattern.toModulePath(artifact) + "/maven-metadata.xml";
+                if (!searched.add(metadataLocation)) {
+                    return;
+                }
+                MavenMetadata mavenMetaData = mavenMetadataLoader.load(metadataLocation);
+                add(mavenMetaData.versions);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/PatternBasedResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/PatternBasedResolver.java
new file mode 100644
index 0000000..e71e0e0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/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.resolver;
+
+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<String> getIvyPatterns();
+
+    List<String> getArtifactPatterns();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourcePattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourcePattern.java
new file mode 100644
index 0000000..fe67b66
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourcePattern.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.api.internal.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public interface ResourcePattern {
+    /**
+     * Returns the path to the given artifact.
+     */
+    String toPath(Artifact artifact);
+
+    /**
+     * Returns the path to the given artifact, without substituting the version placeholders.
+     */
+    String toPathWithoutRevision(Artifact artifact);
+
+    /**
+     * Returns the path to the module for the given artifact.
+     */
+    String toModulePath(Artifact artifact);
+
+    /**
+     * Returns the path to the module version for the given artifact.
+     */
+    String toModuleVersionPath(Artifact artifact);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionLister.java
new file mode 100644
index 0000000..3034b1a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionLister.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.api.internal.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ResourceVersionLister implements VersionLister {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceVersionLister.class);
+    private static final String REVISION_TOKEN = IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY);
+    public static final int REV_TOKEN_LENGTH = REVISION_TOKEN.length();
+
+    private final ExternalResourceRepository repository;
+    private final String fileSeparator = "/";
+
+    public ResourceVersionLister(ExternalResourceRepository repository) {
+        this.repository = repository;
+    }
+
+    public VersionList getVersionList(final ModuleRevisionId moduleRevisionId) {
+        return new DefaultVersionList() {
+            final Set<String> directories = new HashSet<String>();
+
+            public void visit(ResourcePattern resourcePattern, Artifact artifact) throws ResourceNotFoundException, ResourceException {
+                String partiallyResolvedPattern = resourcePattern.toPathWithoutRevision(artifact);
+                LOGGER.debug("Listing all in {}", partiallyResolvedPattern);
+                try {
+                    List<String> versionStrings = listRevisionToken(partiallyResolvedPattern);
+                    add(versionStrings);
+                } catch (ResourceNotFoundException e) {
+                    throw e;
+                } catch (Exception e) {
+                    throw new ResourceException(String.format("Could not list versions using %s.", resourcePattern), e);
+                }
+            }
+
+            // lists all the values a revision token listed by a given url lister
+            private List<String> listRevisionToken(String pattern) throws IOException {
+                pattern = standardize(pattern);
+                if (!pattern.contains(REVISION_TOKEN)) {
+                    LOGGER.debug("revision token not defined in pattern {}.", pattern);
+                    return Collections.emptyList();
+                }
+                String prefix = pattern.substring(0, pattern.indexOf(REVISION_TOKEN));
+                if (revisionMatchesDirectoryName(pattern)) {
+                    return listAll(prefix);
+                } else {
+                    int parentFolderSlashIndex = prefix.lastIndexOf(fileSeparator);
+                    String revisionParentFolder = parentFolderSlashIndex == -1 ? "" : prefix.substring(0, parentFolderSlashIndex + 1);
+                    LOGGER.debug("using {} to list all in {} ", repository, revisionParentFolder);
+                    if (!directories.add(revisionParentFolder)) {
+                        return Collections.emptyList();
+                    }
+                    List<String> all = repository.list(revisionParentFolder);
+                    if (all == null) {
+                        throw new ResourceNotFoundException(String.format("Cannot list versions from %s.", revisionParentFolder));
+                    }
+                    LOGGER.debug("found {} urls", all.size());
+                    Pattern regexPattern = createRegexPattern(pattern, parentFolderSlashIndex);
+                    List<String> ret = filterMatchedValues(all, regexPattern);
+                    LOGGER.debug("{} matched {}" + pattern, ret.size(), pattern);
+                    return ret;
+                }
+            }
+
+            private String standardize(String source) {
+                return source.replace('\\', '/');
+            }
+
+            private List<String> filterMatchedValues(List<String> all, final Pattern p) {
+                List<String> ret = new ArrayList<String>(all.size());
+                for (String path : all) {
+                    Matcher m = p.matcher(path);
+                    if (m.matches()) {
+                        String value = m.group(1);
+                        ret.add(value);
+                    }
+                }
+                return ret;
+            }
+
+            private Pattern createRegexPattern(String pattern, int prefixLastSlashIndex) {
+                int endNameIndex = pattern.indexOf(fileSeparator, prefixLastSlashIndex + 1);
+                String namePattern;
+                if (endNameIndex != -1) {
+                    namePattern = pattern.substring(prefixLastSlashIndex + 1, endNameIndex);
+                } else {
+                    namePattern = pattern.substring(prefixLastSlashIndex + 1);
+                }
+                namePattern = namePattern.replaceAll("\\.", "\\\\.");
+
+                String acceptNamePattern = ".*?"
+                        + namePattern.replaceAll("\\[revision\\]", "([^" + fileSeparator + "]+)")
+                        + "($|" + fileSeparator + ".*)";
+
+                return Pattern.compile(acceptNamePattern);
+            }
+
+            private boolean revisionMatchesDirectoryName(String pattern) {
+                int startToken = pattern.indexOf(REVISION_TOKEN);
+                if (startToken > 0 && !pattern.substring(startToken - 1, startToken).equals(fileSeparator)) {
+                    // previous character is not a separator
+                    return false;
+                }
+                int endToken = startToken + REV_TOKEN_LENGTH;
+                if (endToken < pattern.length() && !pattern.substring(endToken, endToken + 1).equals(fileSeparator)) {
+                    // next character is not a separator
+                    return false;
+                }
+                return true;
+            }
+
+            private List<String> listAll(String parent) throws IOException {
+                if (!directories.add(parent)) {
+                    return Collections.emptyList();
+                }
+                LOGGER.debug("using {} to list all in {}", repository, parent);
+                List<String> fullPaths = repository.list(parent);
+                if (fullPaths == null) {
+                    throw new ResourceNotFoundException(String.format("Cannot list versions from %s.", parent));
+                }
+                LOGGER.debug("found {} resources", fullPaths.size());
+                return extractVersionInfoFromPaths(fullPaths);
+            }
+
+            private List<String> extractVersionInfoFromPaths(List<String> paths) {
+                List<String> ret = new ArrayList<String>(paths.size());
+                for (String fullpath : paths) {
+                    if (fullpath.endsWith(fileSeparator)) {
+                        fullpath = fullpath.substring(0, fullpath.length() - 1);
+                    }
+                    int slashIndex = fullpath.lastIndexOf(fileSeparator);
+                    ret.add(fullpath.substring(slashIndex + 1));
+                }
+                return ret;
+            }
+        };
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionList.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionList.java
new file mode 100644
index 0000000..16bf49d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionList.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.api.internal.artifacts.repositories.resolver;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.api.internal.resource.ResourceNotFoundException;
+
+import java.util.List;
+import java.util.Set;
+
+public interface VersionList {
+    /**
+     * <p>Adds those versions available for the given pattern.</p>
+     *
+     * @throws ResourceNotFoundException If information for versions cannot be found.
+     * @throws ResourceException If information for versions cannot be loaded.
+     */
+    void visit(ResourcePattern pattern, Artifact artifact) throws ResourceNotFoundException, ResourceException;
+
+    Set<String> getVersionStrings();
+
+    boolean isEmpty();
+
+    List<String> sortLatestFirst(LatestStrategy latestStrategy);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionLister.java
new file mode 100644
index 0000000..975c336
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/VersionLister.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.repositories.resolver;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public interface VersionLister {
+    /**
+     * Creates an empty version list for the given module version. Call {@link VersionList#visit(String, org.apache.ivy.core.module.descriptor.Artifact)} to search for versions.
+     */
+    VersionList getVersionList(ModuleRevisionId moduleRevisionId);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListener.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListener.java
new file mode 100644
index 0000000..8013e05
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListener.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.artifacts.repositories.transport;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.gradle.api.internal.externalresource.transfer.AbstractProgressLoggingHandler;
+import org.gradle.api.internal.externalresource.transfer.ResourceOperation;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class ProgressLoggingTransferListener extends AbstractProgressLoggingHandler implements TransferListener {
+    private final Class loggingClass;
+    private ResourceOperation resourceOperation;
+
+    public ProgressLoggingTransferListener(ProgressLoggerFactory progressLoggerFactory, Class loggingClass) {
+        super(progressLoggerFactory);
+        this.loggingClass = loggingClass;
+    }
+
+    public void transferProgress(TransferEvent evt) {
+        final Resource resource = evt.getResource();
+        if (resource.isLocal()) {
+            return;
+        }
+        final int eventType = evt.getEventType();
+        if (eventType == TransferEvent.TRANSFER_STARTED) {
+            resourceOperation = createResourceOperation(resource.getName(), getRequestType(evt), loggingClass, evt.getTotalLength());
+        }
+        if (eventType == TransferEvent.TRANSFER_PROGRESS) {
+            resourceOperation.logProcessedBytes(evt.getLength());
+        }
+        if (eventType == TransferEvent.TRANSFER_COMPLETED) {
+            resourceOperation.completed();
+        }
+    }
+
+    private ResourceOperation.Type getRequestType(TransferEvent evt) {
+        if (evt.getRequestType() == TransferEvent.REQUEST_PUT) {
+            return ResourceOperation.Type.upload;
+        } else {
+            return ResourceOperation.Type.download;
+        }
+    }
+}
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
index 240dd12..156e7d9 100644
--- 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
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.artifacts.repositories.transport;
 
 import org.apache.ivy.plugins.resolver.AbstractResolver;
-import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 
 import java.net.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
index bb6041c..3a645bc 100644
--- 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
@@ -16,84 +16,37 @@
 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.api.internal.file.TemporaryFileProvider;
 import org.gradle.logging.ProgressLoggerFactory;
 
-import java.net.URI;
-
 public class RepositoryTransportFactory {
-    private final TransferListener transferListener;
     private final RepositoryCacheManager downloadingCacheManager;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
     private final RepositoryCacheManager localCacheManager;
+    private final ProgressLoggerFactory progressLoggerFactory;
 
     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");
+                                      RepositoryCacheManager localCacheManager,
+                                      RepositoryCacheManager downloadingCacheManager,
+                                      TemporaryFileProvider temporaryFileProvider,
+                                      CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.localCacheManager = localCacheManager;
+        this.downloadingCacheManager = downloadingCacheManager;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
     }
 
     public RepositoryTransport createHttpTransport(String name, PasswordCredentials credentials) {
-        return decorate(new HttpTransport(name, credentials, downloadingCacheManager));
+        return new HttpTransport(name, credentials, downloadingCacheManager, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex);
     }
 
     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);
-        }
+        return new FileTransport(name, localCacheManager, temporaryFileProvider);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResult.java
new file mode 100644
index 0000000..09d2ce0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResult.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.result;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.ClosureBackedAction;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 8/10/12
+ */
+public class DefaultResolutionResult implements ResolutionResult {
+
+    private final ResolvedModuleVersionResult root;
+
+    public DefaultResolutionResult(ResolvedModuleVersionResult root) {
+        assert root != null;
+        this.root = root;
+    }
+
+    public ResolvedModuleVersionResult getRoot() {
+        return root;
+    }
+
+    public Set<? extends DependencyResult> getAllDependencies() {
+        final Set<DependencyResult> out = new LinkedHashSet<DependencyResult>();
+        allDependencies(new Action<DependencyResult>() {
+            public void execute(DependencyResult dep) {
+                out.add(dep);
+            }
+        });
+        return out;
+    }
+
+    public void allDependencies(Action<? super DependencyResult> action) {
+        eachElement(root, Actions.doNothing(), action, new HashSet<ResolvedModuleVersionResult>());
+    }
+
+    public void allDependencies(final Closure closure) {
+        allDependencies(new ClosureBackedAction<DependencyResult>(closure));
+    }
+
+    private void eachElement(ResolvedModuleVersionResult node,
+                             Action<? super ResolvedModuleVersionResult> moduleAction, Action<? super DependencyResult> dependencyAction,
+                             Set<ResolvedModuleVersionResult> visited) {
+        if (!visited.add(node)) {
+            return;
+        }
+        moduleAction.execute(node);
+        for (DependencyResult d : node.getDependencies()) {
+            dependencyAction.execute(d);
+            if (d instanceof ResolvedDependencyResult) {
+                eachElement(((ResolvedDependencyResult) d).getSelected(), moduleAction, dependencyAction, visited);
+            }
+        }
+    }
+
+    public Set<ResolvedModuleVersionResult> getAllModuleVersions() {
+        final Set<ResolvedModuleVersionResult> out = new LinkedHashSet<ResolvedModuleVersionResult>();
+        eachElement(root, Actions.doNothing(), Actions.doNothing(), out);
+        return out;
+    }
+
+    public void allModuleVersions(final Action<? super ResolvedModuleVersionResult> action) {
+        eachElement(root, action, Actions.doNothing(), new HashSet<ResolvedModuleVersionResult>());
+    }
+
+    public void allModuleVersions(final Closure closure) {
+        allModuleVersions(new ClosureBackedAction<ResolvedModuleVersionResult>(closure));
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.java
new file mode 100644
index 0000000..71e1ed4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedDependencyResult.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.artifacts.result;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+
+/**
+ * by Szczepan Faber, created at: 7/26/12
+ */
+public class DefaultResolvedDependencyResult implements ResolvedDependencyResult {
+
+    private final ModuleVersionSelector requested;
+    private final ResolvedModuleVersionResult selected;
+    private final ResolvedModuleVersionResult from;
+
+    public DefaultResolvedDependencyResult(ModuleVersionSelector requested, ResolvedModuleVersionResult selected, ResolvedModuleVersionResult from) {
+        assert requested != null;
+        assert selected != null;
+        assert from != null;
+
+        this.from = from;
+        this.requested = requested;
+        this.selected = selected;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public ResolvedModuleVersionResult getSelected() {
+        return selected;
+    }
+
+    public ResolvedModuleVersionResult getFrom() {
+        return from;
+    }
+
+    @Override
+    public String toString() {
+        return ResolvedDependencyResultPrinter.print(this);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.java
new file mode 100644
index 0000000..e976424
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResult.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.artifacts.result;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+* by Szczepan Faber, created at: 8/10/12
+*/
+public class DefaultResolvedModuleVersionResult implements ResolvedModuleVersionResult {
+    private final ModuleVersionIdentifier id;
+    private final Set<DependencyResult> dependencies = new LinkedHashSet<DependencyResult>();
+    private final Set<ResolvedDependencyResult> dependents = new LinkedHashSet<ResolvedDependencyResult>();
+    private final ModuleVersionSelectionReason selectionReason;
+
+    public DefaultResolvedModuleVersionResult(ModuleVersionIdentifier id) {
+        this(id, VersionSelectionReasons.REQUESTED);
+    }
+
+    public DefaultResolvedModuleVersionResult(ModuleVersionIdentifier id, ModuleVersionSelectionReason selectionReason) {
+        assert id != null;
+        assert selectionReason != null;
+
+        this.id = id;
+        this.selectionReason = selectionReason;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public Set<DependencyResult> getDependencies() {
+        return Collections.unmodifiableSet(dependencies);
+    }
+
+    public Set<ResolvedDependencyResult> getDependents() {
+        return Collections.unmodifiableSet(dependents);
+    }
+
+    public DefaultResolvedModuleVersionResult addDependency(DependencyResult dependency) {
+        this.dependencies.add(dependency);
+        return this;
+    }
+
+    public DefaultResolvedModuleVersionResult addDependent(ResolvedDependencyResult dependent) {
+        this.dependents.add(dependent);
+        return this;
+    }
+
+    public ModuleVersionSelectionReason getSelectionReason() {
+        return selectionReason;
+    }
+
+    @Override
+    public String toString() {
+        return id.getGroup() + ":" + id.getName() + ":" + id.getVersion();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.java
new file mode 100644
index 0000000..e8d8b92
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/result/DefaultUnresolvedDependencyResult.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.artifacts.result;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
+
+/**
+ * by Szczepan Faber, created at: 7/26/12
+ */
+public class DefaultUnresolvedDependencyResult implements UnresolvedDependencyResult {
+
+    private final ModuleVersionSelector requested;
+    private final Exception failure;
+    private final ResolvedModuleVersionResult from;
+
+    public DefaultUnresolvedDependencyResult(ModuleVersionSelector requested, Exception failure, ResolvedModuleVersionResult from) {
+        assert requested != null;
+        assert failure != null;
+        assert from != null;
+
+        this.from = from;
+        this.failure = failure;
+        this.requested = requested;
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public ResolvedModuleVersionResult getFrom() {
+        return from;
+    }
+
+    @Override
+    public String toString() {
+        return requested.getGroup() + ":" + requested.getName() + ":" + requested.getVersion() + " - " + failure.getMessage();
+    }
+}
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
index 833a60b..72610c1 100644
--- 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
@@ -15,23 +15,26 @@
  */
 package org.gradle.api.internal.externalresource;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.util.CopyProgressListener;
-import org.apache.ivy.util.FileUtil;
 
 import java.io.*;
 
 public abstract class AbstractExternalResource implements ExternalResource {
-    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+
+    public void writeTo(File destination) throws IOException {
         FileOutputStream output = new FileOutputStream(destination);
-        writeTo(output, progress);
-        output.close();
+        try {
+            writeTo(output);
+        } finally {
+            output.close();
+        }
     }
 
-    public void writeTo(OutputStream output, CopyProgressListener progress) throws IOException {
+    public void writeTo(OutputStream output) throws IOException {
         InputStream input = openStream();
         try {
-            FileUtil.copy(input, output, progress);
+            IOUtils.copy(input, output);
         } finally {
             input.close();
         }
@@ -44,5 +47,4 @@ public abstract class AbstractExternalResource implements ExternalResource {
 
     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
index 983795a..a588007 100644
--- 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
@@ -16,7 +16,6 @@
 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;
 
@@ -25,9 +24,12 @@ import java.io.IOException;
 import java.io.OutputStream;
 
 public interface ExternalResource extends Resource {
-    void writeTo(File destination, CopyProgressListener progress) throws IOException;
+    void writeTo(File destination) throws IOException;
 
-    void writeTo(OutputStream destination, CopyProgressListener progress) throws IOException;
+    /**
+     * Writes to the given stream. Does not close the stream.
+     */
+    void writeTo(OutputStream destination) throws IOException;
 
     void close() throws IOException;
 
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
index 7a428b6..004b574 100644
--- 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
@@ -16,10 +16,8 @@
 
 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;
 
@@ -39,10 +37,6 @@ public class MetaDataOnlyExternalResource extends AbstractExternalResource {
         this.local = local;
     }
 
-    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
     public ExternalResourceMetaData getMetaData() {
         return metaData;
     }
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
index 1550906..f4434d6 100644
--- 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
@@ -15,7 +15,6 @@
  */
 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;
@@ -53,24 +52,24 @@ public class CachedExternalResourceAdapter extends LocalFileStandInExternalResou
         return cached.getContentLength();
     }
 
-    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+    public void writeTo(File destination) throws IOException {
         try {
-            super.writeTo(destination, progress);
+            super.writeTo(destination);
         } catch (IOException e) {
-            downloadResourceDirect(destination, progress);
+            downloadResourceDirect(destination);
             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);
+            downloadResourceDirect(destination);
         }
     }
 
-    private void downloadResourceDirect(File destination, CopyProgressListener progress) throws IOException {
+    private void downloadResourceDirect(File destination) throws IOException {
         // Perform a regular download, without considering external caches
-        accessor.getResource(getName()).writeTo(destination, progress);
+        accessor.getResource(getName()).writeTo(destination);
     }
 
 }
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
index dbf56c3..25837e4 100644
--- 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
@@ -61,7 +61,7 @@ public class DefaultCachedExternalResourceIndex<K extends Serializable> implemen
     }
 
     private String operationName(String action) {
-        return String.format("%s from artifact resolution cache '%s'", action, persistentCacheFile.getName());
+        return String.format("%s artifact resolution cache '%s'", action, persistentCacheFile.getName());
     }
 
     public void store(final K key, final File artifactFile, ExternalResourceMetaData externalResourceMetaData) {
@@ -80,7 +80,7 @@ public class DefaultCachedExternalResourceIndex<K extends Serializable> implemen
     }
 
     private void storeInternal(final K key, final DefaultCachedExternalResource entry) {
-        cacheLockingManager.useCache(operationName("store"), new Runnable() {
+        cacheLockingManager.useCache(operationName("store into"), new Runnable() {
             public void run() {
                 getPersistentCache().put(key, entry);
             }
@@ -92,7 +92,7 @@ public class DefaultCachedExternalResourceIndex<K extends Serializable> implemen
             throw new IllegalArgumentException("key cannot be null");
         }
 
-        return cacheLockingManager.useCache(operationName("lookup"), new Factory<DefaultCachedExternalResource>() {
+        return cacheLockingManager.useCache(operationName("lookup from"), new Factory<DefaultCachedExternalResource>() {
             public DefaultCachedExternalResource create() {
                 DefaultCachedExternalResource found = getPersistentCache().get(key);
                 if (found == null) {
@@ -112,7 +112,7 @@ public class DefaultCachedExternalResourceIndex<K extends Serializable> implemen
             throw new IllegalArgumentException("key cannot be null");
         }
 
-        cacheLockingManager.useCache(operationName("clear"), new Runnable() {
+        cacheLockingManager.useCache(operationName("clear from"), new Runnable() {
             public void run() {
                 getPersistentCache().remove(key);
             }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java
deleted file mode 100644
index 538dfdd..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Transformer;
-import org.gradle.api.internal.artifacts.mvnsettings.CannotLocateLocalMavenRepositoryException;
-import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
-import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
-import org.gradle.internal.Factory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-public class LocalMavenLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(LocalMavenLocallyAvailableResourceFinder.class);
-
-    public LocalMavenLocallyAvailableResourceFinder(LocalMavenRepositoryLocator localMavenRepositoryLocator, String pattern) {
-        super(createProducer(localMavenRepositoryLocator, pattern));
-    }
-
-    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final LocalMavenRepositoryLocator localMavenRepositoryLocator, final String pattern) {
-        return new LazyLocalMavenPatternTransformer(localMavenRepositoryLocator, pattern);
-    }
-
-    private static class LazyLocalMavenPatternTransformer implements Transformer<Factory<List<File>>, ArtifactRevisionId> {
-        private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
-        private final String pattern;
-
-        private PatternTransformer patternTransformer;
-        private boolean initialized;
-
-        public LazyLocalMavenPatternTransformer(LocalMavenRepositoryLocator localMavenRepositoryLocator, String pattern) {
-            this.localMavenRepositoryLocator = localMavenRepositoryLocator;
-            this.pattern = pattern;
-        }
-
-        public Factory<List<File>> transform(ArtifactRevisionId original) {
-            if (!initialized) {
-                initializedPatternTransformer();
-            }
-
-            if (patternTransformer != null) {
-                return patternTransformer.transform(original);
-            } else {
-                return new Factory<List<File>>() {
-                    public List<File> create() {
-                        return Collections.emptyList();
-                    }
-                };
-            }
-        }
-
-        private void initializedPatternTransformer() {
-            try {
-                File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
-                this.patternTransformer = new PatternTransformer(localMavenRepository, pattern);
-            } catch (CannotLocateLocalMavenRepositoryException ex) {
-                LOGGER.warn(String.format("Unable to locate local Maven repository."));
-                LOGGER.debug(String.format("Problems while locating local maven repository.", ex));
-            } finally {
-                initialized = true;
-            }
-        }
-    }
-}
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
index 104c485..669d420 100644
--- 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
@@ -17,12 +17,12 @@ 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.CannotLocateLocalMavenRepositoryException;
 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.externalresource.local.*;
 import org.gradle.api.internal.filestore.FileStoreSearcher;
 import org.gradle.internal.Factory;
+import org.gradle.util.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,6 +52,9 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         // The current filestore
         finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ArtifactRevisionId>(fileStore));
 
+        // 1.1, 1.2
+        addForPattern(finders, "artifacts-14", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
         // rc-1, 1.0
         addForPattern(finders, "artifacts-13", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
 
@@ -68,9 +71,16 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         // Milestone 3
         addForPattern(finders, "../cache", "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
 
-        // local maven
-        finders.add(new LocalMavenLocallyAvailableResourceFinder(localMavenRepositoryLocator, "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])"));
 
+        // Maven local
+        try {
+            File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
+            if (localMavenRepository.exists()) {
+                addForPattern(finders, localMavenRepository, "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])");
+            }
+        } catch (CannotLocateLocalMavenRepositoryException ex) {
+            finders.add(new NoMavenLocalRepositoryResourceFinder(ex));
+        }
         return new CompositeLocallyAvailableResourceFinder<ArtifactRevisionId>(finders);
     }
 
@@ -84,4 +94,29 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         }
     }
 
-}
+    private class NoMavenLocalRepositoryResourceFinder implements LocallyAvailableResourceFinder<ArtifactRevisionId> {
+        private final CannotLocateLocalMavenRepositoryException ex;
+        private boolean logged;
+
+        public NoMavenLocalRepositoryResourceFinder(CannotLocateLocalMavenRepositoryException ex) {
+            this.ex = ex;
+        }
+
+        public LocallyAvailableResourceCandidates findCandidates(ArtifactRevisionId criterion) {
+            if(!logged){
+                LOGGER.warn("Unable to locate local Maven repository.");
+                LOGGER.debug("Problems while locating local maven repository.", ex);
+                logged = true;
+            }
+            return new LocallyAvailableResourceCandidates() {
+                public boolean isNone() {
+                    return true;
+                }
+
+                public LocallyAvailableResource findByHashValue(HashValue hashValue) {
+                    return null;
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
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
index b9202b8..c19f639 100644
--- 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
@@ -15,13 +15,66 @@
  */
 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(new PatternTransformer(baseDir, 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/local/ivy/PatternTransformer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternTransformer.java
deleted file mode 100644
index 8baba0f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternTransformer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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 PatternTransformer implements Transformer<Factory<List<File>>, ArtifactRevisionId> {
-
-    private final String pattern;
-    private DirectoryFileTree fileTree;
-
-    public PatternTransformer(File baseDir, String pattern) {
-        this.pattern = pattern;
-        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/transfer/AbstractProgressLoggingHandler.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/AbstractProgressLoggingHandler.java
new file mode 100644
index 0000000..3f80387
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/AbstractProgressLoggingHandler.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.api.internal.externalresource.transfer;
+
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class AbstractProgressLoggingHandler {
+    protected final ProgressLoggerFactory progressLoggerFactory;
+
+    public AbstractProgressLoggingHandler(ProgressLoggerFactory progressLoggerFactory) {
+        this.progressLoggerFactory = progressLoggerFactory;
+    }
+
+    protected ResourceOperation createResourceOperation(String resourceName, ResourceOperation.Type operationType, Class loggingClazz, long contentLength) {
+        ProgressLogger progressLogger = startProgress(String.format("%s %s", operationType.getCapitalized(), resourceName), loggingClazz);
+        return new ResourceOperation(progressLogger, operationType, contentLength);
+    }
+
+    private ProgressLogger startProgress(String description, Class loggingClass) {
+        ProgressLogger progressLogger = progressLoggerFactory.newOperation(loggingClass != null ? loggingClass : getClass());
+        progressLogger.setDescription(description);
+        progressLogger.setLoggingHeader(description);
+        progressLogger.started();
+        return progressLogger;
+    }
+}
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
index 4e7047d..64b86a9 100644
--- 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
@@ -18,13 +18,12 @@ 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;
+    ExternalResource getResource(String source, @Nullable LocallyAvailableResourceCandidates localCandidates) 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
index a3af7e2..97c97b0 100644
--- 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
@@ -21,6 +21,7 @@ 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.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResource;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
@@ -37,13 +38,16 @@ public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExte
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCacheAwareExternalResourceAccessor.class);
 
     private final ExternalResourceAccessor delegate;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
 
-    public DefaultCacheAwareExternalResourceAccessor(ExternalResourceAccessor delegate) {
+    public DefaultCacheAwareExternalResourceAccessor(ExternalResourceAccessor delegate, CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
         this.delegate = delegate;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
     }
 
-    public ExternalResource getResource(final String location, @Nullable LocallyAvailableResourceCandidates localCandidates, @Nullable CachedExternalResource cached) throws IOException {
+    public ExternalResource getResource(final String location, @Nullable LocallyAvailableResourceCandidates localCandidates) throws IOException {
         LOGGER.debug("Constructing external resource: {}", location);
+        CachedExternalResource cached = cachedExternalResourceIndex.lookup(location);
 
         // If we have no caching options, just get the thing directly
         if (cached == null && (localCandidates == null || localCandidates.isNone())) {
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
index eecc59a..e0c9f7d 100644
--- 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
@@ -16,10 +16,12 @@
 
 package org.gradle.api.internal.externalresource.transfer;
 
-import java.io.File;
+import org.gradle.internal.Factory;
+
 import java.io.IOException;
+import java.io.InputStream;
 
 public interface ExternalResourceUploader {
-    
-    void upload(File source, String destination, boolean overwrite) throws IOException;
+
+    void upload(Factory<InputStream> sourceFactory, Long contentLength, String destination) throws IOException;
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessor.java
new file mode 100644
index 0000000..32182a8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessor.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.externalresource.transfer;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.hash.HashValue;
+
+import java.io.*;
+
+public class ProgressLoggingExternalResourceAccessor extends AbstractProgressLoggingHandler implements ExternalResourceAccessor {
+    private final ExternalResourceAccessor delegate;
+
+    public ProgressLoggingExternalResourceAccessor(ExternalResourceAccessor delegate, ProgressLoggerFactory progressLoggerFactory) {
+        super(progressLoggerFactory);
+        this.delegate = delegate;
+    }
+
+    public ExternalResource getResource(String location) throws IOException {
+        ExternalResource resource = delegate.getResource(location);
+        if (resource != null) {
+            return new ProgressLoggingExternalResource(resource);
+        } else {
+            return null;
+        }
+    }
+
+    @Nullable
+    public HashValue getResourceSha1(String location) {
+        return delegate.getResourceSha1(location);
+    }
+
+    @Nullable
+    public ExternalResourceMetaData getMetaData(String location) throws IOException {
+        return delegate.getMetaData(location);
+    }
+
+    private class ProgressLoggingExternalResource implements ExternalResource {
+        private ExternalResource resource;
+
+        private ProgressLoggingExternalResource(ExternalResource resource) {
+            this.resource = resource;
+        }
+
+        /**
+         * This redirect allows us to deprecate ExternalResource#writeto and replace usages later.
+         */
+        public void writeTo(File destination) throws IOException {
+            FileOutputStream output = new FileOutputStream(destination);
+            try {
+                writeTo(output);
+            } finally {
+                output.close();
+            }
+        }
+
+        public void writeTo(OutputStream outputStream) throws IOException {
+            final ResourceOperation downloadOperation = createResourceOperation(resource.getName(), ResourceOperation.Type.download, getClass(), resource.getContentLength());
+            final ProgressLoggingOutputStream progressLoggingOutputStream = new ProgressLoggingOutputStream(outputStream, downloadOperation);
+            try {
+                resource.writeTo(progressLoggingOutputStream);
+            } finally {
+                downloadOperation.completed();
+            }
+        }
+
+        public Resource clone(String cloneName) {
+            return resource.clone(cloneName);
+        }
+
+        public void close() throws IOException {
+            resource.close();
+        }
+
+        @Nullable
+        public ExternalResourceMetaData getMetaData() {
+            return resource.getMetaData();
+        }
+
+        public String getName() {
+            return resource.getName();
+        }
+
+        public long getLastModified() {
+            return resource.getLastModified();
+        }
+
+        public long getContentLength() {
+            return resource.getContentLength();
+        }
+
+        public boolean exists() {
+            return resource.exists();
+        }
+
+        public boolean isLocal() {
+            return resource.isLocal();
+        }
+
+        public InputStream openStream() throws IOException {
+            return resource.openStream();
+        }
+
+        public String toString(){
+            return resource.toString();
+        }
+    }
+
+    private class ProgressLoggingOutputStream extends OutputStream {
+        private OutputStream outputStream;
+        private final ResourceOperation resourceOperation;
+
+        public ProgressLoggingOutputStream(OutputStream outputStream, ResourceOperation resourceOperation) {
+            this.outputStream = outputStream;
+            this.resourceOperation = resourceOperation;
+        }
+
+        @Override
+        public void flush() throws IOException {
+            outputStream.flush();
+        }
+
+        @Override
+        public void close() throws IOException {
+            outputStream.close();
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            outputStream.write(b);
+            resourceOperation.logProcessedBytes(1l);
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            outputStream.write(b, off, len);
+            resourceOperation.logProcessedBytes(len);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploader.java
new file mode 100644
index 0000000..947a65e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploader.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.externalresource.transfer;
+
+import org.gradle.internal.Factory;
+import org.gradle.logging.ProgressLoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProgressLoggingExternalResourceUploader extends AbstractProgressLoggingHandler implements ExternalResourceUploader {
+    private final ExternalResourceUploader delegate;
+
+    public ProgressLoggingExternalResourceUploader(ExternalResourceUploader delegate, ProgressLoggerFactory progressLoggerFactory) {
+        super(progressLoggerFactory);
+        this.delegate = delegate;
+    }
+    public void upload(final Factory<InputStream> source, final Long contentLength, String destination) throws IOException {
+        final ResourceOperation uploadOperation = createResourceOperation(destination, ResourceOperation.Type.upload, getClass(), contentLength);
+
+        try {
+            delegate.upload(new Factory<InputStream>() {
+                public InputStream create() {
+                    return new ProgressLoggingInputStream(source.create(), uploadOperation);
+                }
+            }, contentLength, destination);
+        } finally {
+            uploadOperation.completed();
+        }
+    }
+
+    private class ProgressLoggingInputStream extends InputStream {
+        private InputStream inputStream;
+        private final ResourceOperation resourceOperation;
+
+        public ProgressLoggingInputStream(InputStream inputStream, ResourceOperation resourceOperation) {
+            this.inputStream = inputStream;
+            this.resourceOperation = resourceOperation;
+        }
+
+        @Override
+        public void close() throws IOException {
+            inputStream.close();
+        }
+
+        @Override
+        public int read() throws IOException {
+            int result = inputStream.read();
+            if (result >= 0) {
+                doLogProgress(1);
+            }
+            return result;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            int read = inputStream.read(b, off, len);
+            if (read > 0) {
+                doLogProgress(read);
+            }
+            return read;
+        }
+
+        private void doLogProgress(long numberOfBytes) {
+            resourceOperation.logProcessedBytes(numberOfBytes);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperation.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperation.java
new file mode 100644
index 0000000..b1a8102
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperation.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.transfer;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.logging.ProgressLogger;
+
+public class ResourceOperation {
+    public enum Type{
+        download,
+        upload;
+
+        public String getCapitalized() {
+            return StringUtils.capitalize(toString());
+        }
+    }
+    private final ProgressLogger progressLogger;
+    private final Type operationType;
+    private final String contentLengthString;
+
+    private long loggedKBytes;
+    private long totalProcessedBytes;
+
+    public ResourceOperation(ProgressLogger progressLogger, Type type, long contentLength) {
+        this.progressLogger = progressLogger;
+        this.operationType = type;
+        this.contentLengthString = getLengthText(contentLength != 0 ? contentLength : null);
+    }
+
+    private 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 void logProcessedBytes(long processedBytes) {
+        totalProcessedBytes += processedBytes;
+        long processedKB = totalProcessedBytes / 1024;
+        if (processedKB > loggedKBytes) {
+            loggedKBytes = processedKB;
+            final String progressMessage = String.format("%s/%s %sed", getLengthText(totalProcessedBytes), contentLengthString, operationType);
+            progressLogger.progress(progressMessage);
+        }
+    }
+
+    public void completed() {
+        this.progressLogger.completed();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/DefaultExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/DefaultExternalResourceRepository.java
new file mode 100644
index 0000000..c0ab6da
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/DefaultExternalResourceRepository.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.transfer.CacheAwareExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.List;
+
+public class DefaultExternalResourceRepository implements ExternalResourceRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExternalResourceRepository.class);
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final String name;
+    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, TemporaryFileProvider temporaryFileProvider,
+                                             CacheAwareExternalResourceAccessor cacheAwareAccessor) {
+        this.name = name;
+        this.accessor = accessor;
+        this.uploader = uploader;
+        this.lister = lister;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cacheAwareAccessor = cacheAwareAccessor;
+    }
+
+    public ExternalResource getResource(String source) throws IOException {
+        return accessor.getResource(source);
+    }
+
+    public ExternalResource getResource(String source, LocallyAvailableResourceCandidates localCandidates) throws IOException {
+        return cacheAwareAccessor.getResource(source, localCandidates);
+    }
+
+    public ExternalResourceMetaData getResourceMetaData(String source) throws IOException {
+        return accessor.getMetaData(source);
+    }
+
+    public void put(File source, String destination) throws IOException {
+        doPut(source, destination);
+        putChecksum("SHA1", 40, source, destination);
+    }
+
+    private void putChecksum(String algorithm, int checksumlength, File source, String destination) throws IOException {
+        File checksumFile = createChecksumFile(source, algorithm, checksumlength);
+        String checksumDestination = destination + "." + algorithm.toLowerCase();
+        doPut(checksumFile, checksumDestination);
+    }
+
+    private File createChecksumFile(File src, String algorithm, int checksumlength) {
+        File csFile = temporaryFileProvider.createTemporaryFile("gradle_upload", algorithm);
+        HashValue hash = HashUtil.createHash(src, algorithm);
+        String formattedHashString = formatHashString(hash.asHexString(), checksumlength);
+        GFileUtils.writeFile(formattedHashString, csFile, "US-ASCII");
+        return csFile;
+    }
+
+    private String formatHashString(String hashKey, int length) {
+        while (hashKey.length() < length) {
+            hashKey = "0" + hashKey;
+        }
+        return hashKey;
+    }
+
+    protected void doPut(final File source, String destination) throws IOException {
+        LOGGER.debug("Attempting to put resource {}.", destination);
+        assert source.isFile();
+        try {
+            uploader.upload(new Factory<InputStream>() {
+                public InputStream create() {
+                    try {
+                        return new FileInputStream(source);
+                    } catch (FileNotFoundException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+            }, source.length(), destination);
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public List<String> list(String parent) throws IOException {
+        return lister.list(parent);
+    }
+
+    public String toString() {
+        return name;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/ExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/ExternalResourceRepository.java
new file mode 100644
index 0000000..227ccb8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/ExternalResourceRepository.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.transport;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public interface ExternalResourceRepository {
+
+    /**
+     * Attempts to fetch the given resource.
+     *
+     * @return null if the resource is not found.
+     */
+    ExternalResource getResource(String source) throws IOException;
+
+    /**
+     * Attempts to fetch the given resource.
+     *
+     * @return null if the resource is not found.
+     */
+    ExternalResource getResource(String source, @Nullable LocallyAvailableResourceCandidates localCandidates) throws IOException;
+
+    /**
+     * Transfer a resource to the repository
+     *
+     * @param source The local file to be transferred.
+     * @param destination Where to transfer the resource.
+     * @throws IOException On publication failure.
+     */
+    void put(File source, String destination) 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
+     */
+    @Nullable
+    ExternalResourceMetaData getResourceMetaData(String source) throws IOException;
+
+    /**
+     * Return a listing of resources names
+     *
+     * @param parent The parent directory from which to generate the listing.
+     * @return A listing of the parent directory's file content, as a List of String.
+     * @throws IOException On listing failure.
+     */
+    List<String> list(String parent) throws IOException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
index 2af21dc..b6ab96b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.externalresource.transport.file;
 
-import org.apache.ivy.util.FileUtil;
+import org.apache.commons.io.IOUtils;
 import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
 import org.gradle.api.internal.externalresource.local.DefaultLocallyAvailableResource;
@@ -23,15 +23,19 @@ import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaDat
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
+import org.gradle.internal.Factory;
 import org.gradle.util.hash.HashValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
 import java.util.ArrayList;
 import java.util.List;
 
 public class FileResourceConnector implements ExternalResourceLister, ExternalResourceAccessor, ExternalResourceUploader {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(FileResourceConnector.class);
+
     public List<String> list(String parent) throws IOException {
         File dir = getFile(parent);
         if (dir.exists() && dir.isDirectory()) {
@@ -47,13 +51,24 @@ public class FileResourceConnector implements ExternalResourceLister, ExternalRe
         return null;
     }
 
-    public void upload(File source, String destination, boolean overwrite) throws IOException {
+    public void upload(Factory<InputStream> source, Long contentLength, String destination) throws IOException {
         File target = getFile(destination);
-        if (!overwrite && target.exists()) {
-            throw new IOException("Could not copy file " + source + " to " + target + ": target already exists and overwrite is false");
-        }
-        if (!FileUtil.copy(source, target, null, overwrite)) {
-            throw new IOException("File copy failed from " + source + " to " + target);
+        if (!target.canWrite()) {
+            target.delete();
+        } // if target is writable, the copy will overwrite it without requiring a delete
+        target.getParentFile().mkdirs();
+        FileOutputStream fileOutputStream = new FileOutputStream(target);
+        try {
+            InputStream sourceInputStream = source.create();
+            try {
+                if (IOUtils.copy(sourceInputStream, fileOutputStream) == -1) {
+                    throw new IOException(String.format("File copy failed from %s to %s", source, target));
+                }
+            } finally {
+                sourceInputStream.close();
+            }
+        } finally {
+            fileOutputStream.close();
         }
     }
 
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
index bf15590..7090de6 100644
--- 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
@@ -17,25 +17,33 @@ 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.DefaultExternalResourceRepository;
-import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.Nullable;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.transfer.CacheAwareExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transport.DefaultExternalResourceRepository;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
+import org.gradle.api.internal.file.TemporaryFileProvider;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URI;
 
 public class FileTransport implements RepositoryTransport {
     private final String name;
     private final RepositoryCacheManager repositoryCacheManager;
+    private final TemporaryFileProvider temporaryFileProvider;
 
-    public FileTransport(String name, RepositoryCacheManager repositoryCacheManager) {
+    public FileTransport(String name, RepositoryCacheManager repositoryCacheManager, TemporaryFileProvider temporaryFileProvider) {
         this.name = name;
         this.repositoryCacheManager = repositoryCacheManager;
+        this.temporaryFileProvider = temporaryFileProvider;
     }
 
     public ExternalResourceRepository getRepository() {
         FileResourceConnector connector = new FileResourceConnector();
-        return new DefaultExternalResourceRepository(name, connector, connector, connector);
+        return new DefaultExternalResourceRepository(name, connector, connector, connector, temporaryFileProvider, new NoOpCacheAwareExternalResourceAccessor(connector));
     }
 
     public void configureCacheManager(AbstractResolver resolver) {
@@ -52,4 +60,16 @@ public class FileTransport implements RepositoryTransport {
         }
         return path + "/";
     }
+
+    private static class NoOpCacheAwareExternalResourceAccessor implements CacheAwareExternalResourceAccessor {
+        private final FileResourceConnector connector;
+
+        public NoOpCacheAwareExternalResourceAccessor(FileResourceConnector connector) {
+            this.connector = connector;
+        }
+
+        public ExternalResource getResource(String source, @Nullable LocallyAvailableResourceCandidates localCandidates) throws IOException {
+            return connector.getResource(source);
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java
deleted file mode 100644
index de0d5fe..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.CopyProgressEvent;
-import org.apache.ivy.util.CopyProgressListener;
-
-public class CopyProgressListenerAdapter implements CopyProgressListener {
-    public void start(CopyProgressEvent evt) {
-    }
-
-    public void progress(CopyProgressEvent evt) {
-    }
-
-    public void end(CopyProgressEvent evt) {
-    }
-}
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
index 705a91f..5acf28d 100644
--- 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
@@ -28,20 +28,18 @@ import org.apache.http.client.params.AuthPolicy;
 import org.apache.http.client.protocol.ClientContext;
 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.params.HttpProtocolParams;
 import org.apache.http.protocol.ExecutionContext;
 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.api.internal.resource.UriResource;
 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);
@@ -55,7 +53,7 @@ public class HttpClientConfigurer {
     public void configure(DefaultHttpClient httpClient) {
         NTLMSchemeFactory.register(httpClient);
         configureCredentials(httpClient, httpSettings.getCredentials());
-        configureProxy(httpClient, httpSettings.getProxySettings());
+        configureProxyCredentials(httpClient, httpSettings.getProxySettings());
         configureRetryHandler(httpClient);
         configureUserAgent(httpClient);
     }
@@ -69,11 +67,7 @@ public class HttpClientConfigurer {
         }
     }
 
-    private void configureProxy(DefaultHttpClient httpClient, HttpProxySettings proxySettings) {
-        // Use standard JVM proxy settings
-        ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault());
-        httpClient.setRoutePlanner(routePlanner);
-
+    private void configureProxyCredentials(DefaultHttpClient httpClient, HttpProxySettings proxySettings) {
         HttpProxySettings.HttpProxy proxy = proxySettings.getProxy();
         if (proxy != null && proxy.credentials != null) {
             useCredentials(httpClient, proxy.credentials, proxy.host, proxy.port);
@@ -100,8 +94,7 @@ public class HttpClientConfigurer {
     }
 
     public void configureUserAgent(DefaultHttpClient httpClient) {
-        String userAgent = "Gradle/" + GradleVersion.current().getVersion();
-        HttpProtocolParams.setUserAgent(httpClient.getParams(), userAgent);
+        HttpProtocolParams.setUserAgent(httpClient.getParams(), UriResource.getUserAgentString());
     }
 
     static class PreemptiveAuth implements HttpRequestInterceptor {
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
index b02ca94..6075676 100644
--- 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
@@ -17,12 +17,11 @@
 package org.gradle.api.internal.externalresource.transport.http;
 
 import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
 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.impl.client.*;
 import org.apache.http.protocol.BasicHttpContext;
 import org.apache.http.util.EntityUtils;
 import org.gradle.api.UncheckedIOException;
@@ -37,14 +36,13 @@ import java.io.IOException;
 public class HttpClientHelper {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientHelper.class);
-    private final DefaultHttpClient client = new ContentEncodingHttpClient();
+    private final HttpClient client;
     private final BasicHttpContext httpContext = new BasicHttpContext();
 
-    private final HttpClientConfigurer configurer;
-
     public HttpClientHelper(HttpSettings settings) {
-        configurer = new HttpClientConfigurer(settings);
-        configurer.configure(client);
+        DefaultHttpClient client = new SystemDefaultHttpClient();
+        new HttpClientConfigurer(settings).configure(client);
+        this.client = new DecompressingHttpClient(client);
     }
 
     public HttpResponse performRawHead(String source) {
@@ -70,13 +68,13 @@ public class HttpClientHelper {
         try {
             response = executeGetOrHead(request);
         } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Could not %s '%s'.", method, request.getURI()), e);
+            throw new HttpRequestException(String.format("Could not %s '%s'.", method, request.getURI()), e);
         }
 
         return response;
     }
 
-    private HttpResponse executeGetOrHead(HttpRequestBase method) throws IOException {
+    protected 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)) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpRequestException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpRequestException.java
new file mode 100644
index 0000000..13cf3e9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpRequestException.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.externalresource.transport.http;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.Contextual;
+
+/**
+ * Signals that some error occurred when making an HTTP request.
+ * This is different from a HTTP request returning an HTTP error code.
+ */
+ at Contextual
+public class HttpRequestException extends UncheckedIOException {
+    public HttpRequestException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
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
index 79396cd..796fe03 100644
--- 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
@@ -40,12 +40,12 @@ public class HttpResourceAccessor implements ExternalResourceAccessor {
         this.http = http;
     }
 
-    public ExternalResource getResource(String location) throws IOException {
+    public HttpResponseResource 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) {
+            HttpResponseResource resource = new HttpResponseResource("GET", location, response) {
                 @Override
                 public void close() throws IOException {
                     super.close();
@@ -66,10 +66,8 @@ public class HttpResourceAccessor implements ExternalResourceAccessor {
         return response == null ? null : new HttpResponseResource("HEAD", location, response).getMetaData();
     }
 
-    private ExternalResource recordOpenGetResource(ExternalResource httpResource) {
-        if (httpResource instanceof HttpResponseResource) {
-            openResources.add(httpResource);
-        }
+    private HttpResponseResource recordOpenGetResource(HttpResponseResource httpResource) {
+        openResources.add(httpResource);
         return httpResource;
     }
 
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
index 4e3d1fb..5905481 100644
--- 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
@@ -39,20 +39,24 @@ public class HttpResourceLister implements ExternalResourceLister {
         try {
             baseURI = new URI(parent);
         } catch (URISyntaxException ex) {
-            throw new ResourceException(String.format("Unable to create URI from String '%s' ", parent), ex);
+            throw new ResourceException(String.format("Unable to create URI from string '%s' ", parent), ex);
         }
-        final ExternalResource resource = accessor.getResource(baseURI.toString());
+        final HttpResponseResource resource = accessor.getResource(baseURI.toString());
         if (resource == null) {
             return null;
         }
-        byte[] resourceContent = loadResourceContent(resource);
-        String contentType = getContentType(resource);
-        ApacheDirectoryListingParser directoryListingParser = new ApacheDirectoryListingParser();
         try {
-            List<URI> uris = directoryListingParser.parse(baseURI, resourceContent, contentType);
-            return convertToStringList(uris);
-        } catch (Exception e) {
-            throw new ResourceException("Unable to parse Http directory listing", e);
+            byte[] resourceContent = loadResourceContent(resource);
+            String contentType = resource.getContentType();
+            ApacheDirectoryListingParser directoryListingParser = new ApacheDirectoryListingParser();
+            try {
+                List<URI> uris = directoryListingParser.parse(baseURI, resourceContent, contentType);
+                return convertToStringList(uris);
+            } catch (Exception e) {
+                throw new ResourceException("Unable to parse Http directory listing", e);
+            }
+        } finally {
+            resource.close();
         }
     }
 
@@ -64,26 +68,9 @@ public class HttpResourceLister implements ExternalResourceLister {
         return ret;
     }
 
-    private String getContentType(ExternalResource resource) {
-        if (resource instanceof HttpResponseResource) {
-            return ((HttpResponseResource) resource).getContentType();
-        }
-        return null;
-    }
-
-    byte[] loadResourceContent(ExternalResource resource) throws IOException {
+    private byte[] loadResourceContent(ExternalResource resource) throws IOException {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        try {
-            resource.writeTo(outputStream, new CopyProgressListenerAdapter());
-            return outputStream.toByteArray();
-        } finally {
-            try {
-                outputStream.close();
-            } finally {
-                resource.close();
-            }
-        }
+        resource.writeTo(outputStream);
+        return outputStream.toByteArray();
     }
-
-
 }
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
index 0fd3f92..60ce022 100644
--- 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
@@ -18,12 +18,13 @@ 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.entity.ContentType;
 import org.apache.http.util.EntityUtils;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
+import org.gradle.internal.Factory;
 
-import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 
 public class HttpResourceUploader implements ExternalResourceUploader {
 
@@ -33,9 +34,10 @@ public class HttpResourceUploader implements ExternalResourceUploader {
         this.http = http;
     }
 
-    public void upload(File source, String destination, boolean overwrite) throws IOException {
+    public void upload(Factory<InputStream> source, Long contentLength, String destination) throws IOException {
         HttpPut method = new HttpPut(destination);
-        method.setEntity(new FileEntity(source, "application/octet-stream"));
+        final RepeatableInputStreamEntity entity = new RepeatableInputStreamEntity(source, contentLength, ContentType.APPLICATION_OCTET_STREAM);
+        method.setEntity(entity);
         HttpResponse response = http.performHttpRequest(method);
         EntityUtils.consume(response.getEntity());
         if (!http.wasSuccessful(response)) {
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
index 1ed5c85..26f7876 100644
--- 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
@@ -18,9 +18,15 @@ 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.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.transfer.DefaultCacheAwareExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transport.DefaultExternalResourceRepository;
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.transfer.ProgressLoggingExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transfer.ProgressLoggingExternalResourceUploader;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.logging.ProgressLoggerFactory;
 
 import java.net.URI;
 
@@ -28,18 +34,33 @@ public class HttpTransport implements RepositoryTransport {
     private final String name;
     private final PasswordCredentials credentials;
     private final RepositoryCacheManager repositoryCacheManager;
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
 
-    public HttpTransport(String name, PasswordCredentials credentials, RepositoryCacheManager repositoryCacheManager) {
+    public HttpTransport(String name, PasswordCredentials credentials, RepositoryCacheManager repositoryCacheManager,
+                         ProgressLoggerFactory progressLoggerFactory, TemporaryFileProvider temporaryFileProvider,
+                         CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
         this.name = name;
         this.credentials = credentials;
         this.repositoryCacheManager = repositoryCacheManager;
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
     }
 
     public ExternalResourceRepository getRepository() {
         HttpClientHelper http = new HttpClientHelper(new DefaultHttpSettings(credentials));
-        final HttpResourceAccessor accessor = new HttpResourceAccessor(http);
+        HttpResourceAccessor accessor = new HttpResourceAccessor(http);
+        HttpResourceUploader uploader = new HttpResourceUploader(http);
+        ProgressLoggingExternalResourceAccessor loggingAccessor = new ProgressLoggingExternalResourceAccessor(accessor, progressLoggerFactory);
         return new DefaultExternalResourceRepository(
-                name, accessor, new HttpResourceUploader(http), new HttpResourceLister(accessor)
+                name,
+                accessor,
+                new ProgressLoggingExternalResourceUploader(uploader, progressLoggerFactory),
+                new HttpResourceLister(accessor),
+                temporaryFileProvider,
+                new DefaultCacheAwareExternalResourceAccessor(loggingAccessor, cachedExternalResourceIndex)
         );
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/RepeatableInputStreamEntity.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/RepeatableInputStreamEntity.java
new file mode 100644
index 0000000..f416f75
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/RepeatableInputStreamEntity.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.api.internal.externalresource.transport.http;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ContentType;
+import org.gradle.internal.Factory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class RepeatableInputStreamEntity extends AbstractHttpEntity {
+    private final Factory<InputStream> source;
+    private final Long contentLength;
+
+    public RepeatableInputStreamEntity(Factory<InputStream> source, Long contentLength, ContentType contentType) {
+        super();
+        this.source = source;
+        this.contentLength = contentLength;
+        if (contentType != null) {
+            setContentType(contentType.toString());
+        }
+    }
+
+    public boolean isRepeatable() {
+        return true;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    public InputStream getContent() throws IOException, IllegalStateException {
+        return source.create();
+    }
+
+    public void writeTo(OutputStream outstream) throws IOException {
+        IOUtils.copy(getContent(), outstream);
+    }
+
+    public boolean isStreaming() {
+        return true;
+    }
+}
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
deleted file mode 100644
index 883dc56..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/DefaultFileStoreEntry.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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
deleted file mode 100644
index e7e8771..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java
+++ /dev/null
@@ -1,24 +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.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/GroupedAndNamedUniqueFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java
deleted file mode 100644
index e67c2e9..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java
+++ /dev/null
@@ -1,58 +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.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
deleted file mode 100644
index 6ec2dd6..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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("Unable 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
index aee4dc1..5157e28 100644
--- 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
@@ -20,16 +20,17 @@ 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.file.TemporaryFileProvider;
 import org.gradle.api.internal.filestore.GroupedAndNamedUniqueFileStore;
-import org.gradle.api.internal.filestore.UniquePathFileStore;
+import org.gradle.api.internal.filestore.PathKeyFileStore;
 
 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));
+    public ArtifactRevisionIdFileStore(PathKeyFileStore pathKeyFileStore, TemporaryFileProvider temporaryFileProvider) {
+        super(pathKeyFileStore, temporaryFileProvider, toTransformer(GROUP_PATTERN), toTransformer(NAME_PATTERN));
     }
 
     private static Transformer<String, ArtifactRevisionId> toTransformer(final String pattern) {
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
index e5bbc17..be631ac 100644
--- 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
@@ -38,6 +38,8 @@ public class ArtifactsTestUtils {
             will(returnValue(extension));
             allowing(artifactStub).getExtraAttributes();
             will(returnValue(Collections.emptyMap()));
+            allowing(artifactStub).getQualifiedExtraAttributes();
+            will(returnValue(Collections.emptyMap()));
             allowing(artifactStub).getExtraAttribute(with(org.hamcrest.Matchers.notNullValue(String.class)));
             will(returnValue(null));
         }});
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
index 445acff..f102ef0 100644
--- 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
@@ -36,6 +36,7 @@ import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.internal.TimeProvider
 import spock.lang.Specification
+import org.gradle.api.internal.file.TemporaryFileProvider
 
 class DefaultDependencyManagementServicesTest extends Specification {
     final ServiceRegistry parent = Mock()
@@ -62,6 +63,7 @@ class DefaultDependencyManagementServicesTest extends Specification {
         _ * parent.get(ListenerManager) >> listenerManager
         _ * parent.get(FileLockManager) >> Mock(FileLockManager)
         _ * parent.get(TimeProvider) >> Mock(TimeProvider)
+        _ * parent.get(TemporaryFileProvider) >> Mock(TemporaryFileProvider)
     }
 
     private CacheRepository initCacheRepository() {
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
index f80783b..eb7aba8 100644
--- 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
@@ -26,13 +26,10 @@ class DefaultResolvedArtifactTest extends Specification {
     final Factory artifactSource = Mock()
 
     def "uses extended attributes to determine classifier"() {
-        Artifact ivyArtifact = Mock()
+        Artifact ivyArtifact = ivyArtifact("name", "type", "ext", ['m:classifier': 'classifier'])
         ResolvedDependency dependency = Mock()
         def artifact = new DefaultResolvedArtifact(dependency, ivyArtifact, artifactSource)
 
-        given:
-        _ * ivyArtifact.getExtraAttribute("m:classifier") >> "classifier"
-
         expect:
         artifact.classifier == 'classifier'
     }
@@ -68,7 +65,7 @@ class DefaultResolvedArtifactTest extends Specification {
         _ * artifact.name >> name
         _ * artifact.type >> type
         _ * artifact.ext >> extension
-        _ * artifact.extraAttributes >> attributes
+        _ * artifact.qualifiedExtraAttributes >> attributes
         return artifact
     }
 
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
index a3fa933..d8643ed 100644
--- 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
@@ -16,9 +16,9 @@
 package org.gradle.api.internal.artifacts.ivyservice
 
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
-import spock.lang.Specification
+import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
-import org.gradle.api.artifacts.ResolvedConfiguration
+import spock.lang.Specification
 
 class CacheLockingArtifactDependencyResolverTest extends Specification {
     final CacheLockingManager lockingManager = Mock()
@@ -27,18 +27,18 @@ class CacheLockingArtifactDependencyResolverTest extends Specification {
 
     def "resolves while holding a lock on the cache"() {
         ConfigurationInternal configuration = Mock()
-        ResolvedConfiguration resolvedConfiguration = Mock()
+        ResolverResults resolverResults = Mock()
 
         when:
-        def result = resolver.resolve(configuration)
+        def results = resolver.resolve(configuration)
 
         then:
-        result == resolvedConfiguration
+        results == resolverResults
 
         and:
         1 * lockingManager.useCache("resolve $configuration", !null) >> {
             it[1].create()
         }
-        1 * target.resolve(configuration) >> resolvedConfiguration
+        1 * target.resolve(configuration) >> resolverResults
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.groovy
new file mode 100644
index 0000000..f57f3d4
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableArtifactResolveResultTest.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.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactNotFoundException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException
+import spock.lang.Specification
+
+class DefaultBuildableArtifactResolveResultTest extends Specification {
+    final result = new DefaultBuildableArtifactResolveResult()
+
+    def "cannot get file when no result specified"() {
+        when:
+        result.file
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get metadata when no result specified"() {
+        when:
+        result.externalResourceMetaData
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get failure when no result specified"() {
+        when:
+        result.failure
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get file when resolve failed"() {
+        def failure = new ArtifactResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.file
+
+        then:
+        ArtifactResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get metadata when resolve failed"() {
+        def failure = new ArtifactResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.externalResourceMetaData
+
+        then:
+        ArtifactResolveException e = thrown()
+        e == failure
+    }
+
+    def "fails with not found exception when artifact not found"() {
+        when:
+        result.notFound(new DefaultArtifact(ModuleRevisionId.newInstance("org", "module", "rev"), new Date(), "art", "type", "type"))
+
+        then:
+        result.failure instanceof ArtifactNotFoundException
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
new file mode 100644
index 0000000..360899a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 spock.lang.Specification
+
+class DefaultBuildableModuleVersionResolveResultTest extends Specification {
+    def result = new DefaultBuildableModuleVersionResolveResult()
+
+    def "cannot get id when no result has been specified"() {
+        when:
+        result.id
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get descriptor when no result has been specified"() {
+        when:
+        result.descriptor
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get artifact resolver when no result has been specified"() {
+        when:
+        result.artifactResolver
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get failure when no result has been specified"() {
+        when:
+        result.failure
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'No result has been specified.'
+    }
+
+    def "cannot get id when resolve failed"() {
+        def failure = new ModuleVersionResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.id
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get descriptor when resolve failed"() {
+        def failure = new ModuleVersionResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.descriptor
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get artifact resolver when resolve failed"() {
+        def failure = new ModuleVersionResolveException("broken")
+
+        when:
+        result.failed(failure)
+        result.artifactResolver
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "failure is null when successfully resolved"() {
+        when:
+        result.resolved(Mock(ModuleRevisionId), Mock(ModuleDescriptor), Mock(ArtifactResolver))
+
+        then:
+        result.failure == null
+    }
+
+    def "fails with a not found exception when not found"() {
+        when:
+        result.notFound(Mock(ModuleRevisionId))
+
+        then:
+        result.failure instanceof ModuleVersionNotFoundException
+    }
+}
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
index 9864151..8a2d7cb 100644
--- 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
@@ -16,118 +16,97 @@
 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.artifacts.result.ResolutionResult
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ResolverResults
 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 org.gradle.api.specs.Specs
+import spock.lang.Specification
+
 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));
-        }
+public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
+
+    private delegate = Mock(ArtifactDependencyResolver)
+    private resolvedConfiguration = Mock(ResolvedConfiguration)
+    private resolutionResult = Mock(ResolutionResult)
+    private configuration = Mock(ConfigurationInternal.class, name: 'coolConf')
+    private resolver = new ErrorHandlingArtifactDependencyResolver(delegate);
+
+    void "delegates to backing service"() {
+        given:
+        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, resolutionResult)
+
+        when:
+        ResolverResults outerResults = resolver.resolve(configuration);
+        outerResults.resolvedConfiguration.hasError()
+        outerResults.resolvedConfiguration.rethrowFailure()
+        outerResults.resolvedConfiguration.getFiles(Specs.satisfyAll())
+
+        then:
+        1 * resolvedConfiguration.hasError()
+        1 * resolvedConfiguration.rethrowFailure()
+        1 * resolvedConfiguration.getFiles(Specs.satisfyAll())
+        outerResults.resolutionResult == resolutionResult
+    }
 
-        ResolvedConfiguration resolvedConfiguration = dependencyResolver.resolve(configurationMock);
+    void "wraps operations with the failure"() {
+        given:
+        def failure = new RuntimeException()
+        delegate.resolve(configuration) >> { throw failure }
 
-        context.checking {
-            one(resolvedConfigurationMock).hasError();
-            one(resolvedConfigurationMock).rethrowFailure();
-            one(resolvedConfigurationMock).getFiles(specDummy);
-        }
+        when:
+        ResolverResults results = resolver.resolve(configuration);
+
+        then:
+        results.resolvedConfiguration.hasError()
 
-        resolvedConfiguration.hasError();
-        resolvedConfiguration.rethrowFailure();
-        resolvedConfiguration.getFiles(specDummy);
+        failsWith(failure)
+            .when { results.resolvedConfiguration.rethrowFailure(); }
+            .when { results.resolvedConfiguration.getFiles(Specs.satisfyAll()); }
+            .when { results.resolvedConfiguration.getFirstLevelModuleDependencies(); }
+            .when { results.resolvedConfiguration.getResolvedArtifacts(); }
     }
 
-    @Test
-    public void returnsAnExceptionThrowingConfigurationWhenResolveFails() {
+    void "wraps exceptions thrown by resolved configuration"() {
+        given:
+        def failure = new RuntimeException()
 
-        context.checking {
-            one(resolverMock).resolve(configurationMock);
-            will(throwException(failure));
-        }
+        resolvedConfiguration.rethrowFailure() >> { throw failure }
+        resolvedConfiguration.getFiles(Specs.satisfyAll()) >> { throw failure }
+        resolvedConfiguration.getFirstLevelModuleDependencies() >> { throw failure }
+        resolvedConfiguration.getResolvedArtifacts() >> { throw failure }
 
-        ResolvedConfiguration resolvedConfiguration = dependencyResolver.resolve(configurationMock);
+        delegate.resolve(configuration) >> { new ResolverResults(resolvedConfiguration, resolutionResult) }
 
-        assertThat(resolvedConfiguration.hasError(), equalTo(true));
+        when:
+        ResolverResults results = resolver.resolve(configuration);
 
-        assertFailsWithResolveException {
-            resolvedConfiguration.rethrowFailure();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFiles(specDummy);
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFirstLevelModuleDependencies();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getResolvedArtifacts();
-        }
+        then:
+        failsWith(failure)
+                .when { results.resolvedConfiguration.rethrowFailure(); }
+                .when { results.resolvedConfiguration.getFiles(Specs.satisfyAll()); }
+                .when { results.resolvedConfiguration.getFirstLevelModuleDependencies(); }
+                .when { results.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();
-        }
+    ExceptionFixture failsWith(Throwable failure) {
+        new ExceptionFixture(failure: 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));
+    class ExceptionFixture {
+        Throwable failure
+        ExceptionFixture when(Closure cl) {
+            try {
+                cl();
+                fail();
+            } catch (ResolveException e) {
+                assert e.message == "Could not resolve all dependencies for Mock for type 'ConfigurationInternal' named 'coolConf'."
+                assert e.cause.is(failure);
+            }
+            this
         }
     }
 }
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
index 3244cdf..4b1c550 100644
--- 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
@@ -16,50 +16,55 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 
+import org.gradle.api.artifacts.Module
 import org.gradle.api.artifacts.PublishException
 import org.gradle.api.artifacts.ResolveException
-
+import org.gradle.api.internal.artifacts.ArtifactPublisher
 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
 
 @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 Set configurations = Collections.singleton(configurationMock)
+    private final Module moduleMock = context.mock(Module)
+
     private final RuntimeException failure = new RuntimeException();
     private final ErrorHandlingArtifactPublisher ivyService = new ErrorHandlingArtifactPublisher(artifactPublisherMock);
 
     @Test
     public void publishDelegatesToBackingService() {
         context.checking {
-            one(artifactPublisherMock).publish(configurationMock, null)
+            one(artifactPublisherMock).publish(moduleMock, configurations, null, null)
         }
 
-        ivyService.publish(configurationMock, null)
+        ivyService.publish(moduleMock, configurations, null, null)
     }
 
     @Test
     public void wrapsPublishException() {
         context.checking {
-            one(artifactPublisherMock).publish(configurationMock, null)
+            one(configurationMock).getName(); will(returnValue("name"))
+            one(artifactPublisherMock).publish(moduleMock, configurations, null, null)
             will(throwException(failure))
         }
 
         try {
-            ivyService.publish(configurationMock, null)
+            ivyService.publish(moduleMock, configurations, null, null)
             fail()
         }
         catch(PublishException e) {
-            assertThat e.message, equalTo("Could not publish <config display name>.")
+            assertThat e.message, equalTo("Could not publish configuration: [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
index 46f0907..348b9b1 100644
--- 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
@@ -1,162 +1,166 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.event.EventManager;
-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.internal.artifacts.configurations.*;
-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 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;
-
-/**
- * @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 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 EventManager ivyEventManagerDummy = new EventManager();
-        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();
-
-        setUpIvyFactory(ivySettingsDummy, ivyEventManagerDummy);
-        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, ivyEventManagerDummy);
-        }});
-
-        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(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 EventManager ivyEventManagerDummy) {
-        final Ivy ivyStub = context.mock(Ivy.class);
-        context.checking(new Expectations() {{
-            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
-            will(returnValue(ivyStub));
-
-            allowing(ivyStub).getSettings();
-            will(returnValue(ivySettingsDummy));
-
-            allowing(ivyStub).getEventManager();
-            will(returnValue(ivyEventManagerDummy));
-        }});
-        return ivyStub;
-    }
-}
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.event.EventManager;
+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.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 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");
+    private IvyModuleDescriptorWriter ivyModuleDescriptorWriterMock = context.mock(IvyModuleDescriptorWriter.class);
+
+    @Test
+    public void testPublish() throws IOException, ParseException {
+        final IvySettings ivySettingsDummy = new IvySettings();
+        final EventManager ivyEventManagerDummy = new EventManager();
+        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();
+
+        setUpIvyFactory(ivySettingsDummy, ivyEventManagerDummy);
+        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(ivyDependencyPublisherMock).publish(expectedConfigurations,
+                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, ivyEventManagerDummy);
+            allowing(ivyModuleDescriptorWriterMock).write(fileModuleDescriptorMock, someDescriptorDestination, null);
+        }});
+
+        ivyService.publish(configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination, null);
+    }
+
+    private IvyBackedArtifactPublisher createIvyService() {
+        return new IvyBackedArtifactPublisher(resolverProvider,
+                settingsConverterStub,
+                publishModuleDescriptorConverter,
+                fileModuleDescriptorConverter,
+                ivyFactoryStub,
+                ivyDependencyPublisherMock,
+                ivyModuleDescriptorWriterMock);
+    }
+
+    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(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 EventManager ivyEventManagerDummy) {
+        final Ivy ivyStub = context.mock(Ivy.class);
+        context.checking(new Expectations() {{
+            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
+            will(returnValue(ivyStub));
+
+            allowing(ivyStub).getSettings();
+            will(returnValue(ivySettingsDummy));
+
+            allowing(ivyStub).getEventManager();
+            will(returnValue(ivyEventManagerDummy));
+        }});
+        return ivyStub;
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
new file mode 100644
index 0000000..8c44bee
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.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.internal.artifacts.ivyservice
+
+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.ModuleRevisionId
+import org.gradle.api.Action
+import org.gradle.api.XmlProvider
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+import java.text.SimpleDateFormat
+
+class IvyXmlModuleDescriptorWriterTest extends Specification {
+
+    private @Rule TemporaryFolder temporaryFolder;
+    private ModuleDescriptor md = Mock();
+    private ModuleRevisionId moduleRevisionId = Mock()
+    private ModuleRevisionId resolvedModuleRevisionId = Mock()
+    private PrintWriter printWriter = Mock()
+    def ivyXmlModuleDescriptorWriter = new IvyXmlModuleDescriptorWriter()
+
+    def setup() {
+        1 * md.extraAttributesNamespaces >> [:]
+        1 * md.extraAttributes >> [buildNr: "815"]
+        1 * md.qualifiedExtraAttributes >> [buildNr: "815"]
+        1 * md.getExtraInfo() >> Collections.emptyMap()
+        1 * md.getLicenses() >> Collections.emptyList()
+        2 * md.inheritedDescriptors >> Collections.emptyList()
+        1 * md.resolvedModuleRevisionId >> resolvedModuleRevisionId
+        1 * md.resolvedPublicationDate >> date("20120817120000")
+        1 * md.status >> "integration"
+        1 * resolvedModuleRevisionId.revision >> "1.0"
+        1 * md.moduleRevisionId >> moduleRevisionId;
+        1 * moduleRevisionId.organisation >> "org.test"
+        1 * moduleRevisionId.name >> "projectA"
+        1 * md.configurations >> [mockConfiguration("archives"), mockConfiguration("compile"), mockConfiguration("runtime", ["compile"])]
+        1 * md.configurationsNames >> ["archives", "runtime", "compile"]
+        1 * md.allExcludeRules >> []
+        1 * md.allArtifacts >> [mockArtifact()]
+        1 * md.allDependencyDescriptorMediators >> []
+    }
+
+    def "can create ivy (unmodified) descriptor"() {
+        setup:
+        def dependency1 = mockDependencyDescriptor("Dep1")
+        def dependency2 = mockDependencyDescriptor("Dep2")
+        2 * dependency2.force >> true
+        2 * dependency2.changing >> true
+        1 * md.dependencies >> [dependency1, dependency2]
+        1 * dependency1.transitive >> true
+        when:
+        File ivyFile = temporaryFolder.file("test/ivy/ivy.xml")
+        ivyXmlModuleDescriptorWriter.write(md, ivyFile);
+        then:
+        def ivyModule = new XmlSlurper().parse(ivyFile);
+        assert ivyModule. at version == "2.0"
+        assert ivyModule.info. at organisation == "org.test"
+        assert ivyModule.info. at module == "projectA"
+        assert ivyModule.info. at revision == "1.0"
+        assert ivyModule.info. at status == "integration"
+        assert ivyModule.info. at publication == "20120817120000"
+        assert ivyModule.info. at buildNr == "815"
+        assert ivyModule.configurations.conf.collect {it. at name } == ["archives", "compile", "runtime"]
+        assert ivyModule.publications.artifact.collect {it. at name } == ["testartifact"]
+        assert ivyModule.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:Dep1:1.0", "org.test:Dep2:1.0"]
+    }
+
+    def "can create ivy (modified) descriptor"() {
+        setup:
+        def dependency1 = mockDependencyDescriptor("Dep1")
+        def dependency2 = mockDependencyDescriptor("Dep2")
+        1 * md.dependencies >> [dependency1, dependency2]
+        when:
+        File ivyFile = temporaryFolder.file("test/ivy/ivy.xml")
+        XmlTransformer xmlTransformer = new XmlTransformer()
+        xmlTransformer.addAction(new Action<XmlProvider>() {
+            void execute(XmlProvider xml) {
+                def node = xml.asNode()
+                node.info[0]. at status = "foo"
+                assert node.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:Dep1:1.0", "org.test:Dep2:1.0"]
+                node.dependencies[0].dependency[0]. at name = "changed"
+            }
+        })
+        ivyXmlModuleDescriptorWriter.write(md, ivyFile, xmlTransformer)
+
+        then:
+        def ivyModule = new XmlSlurper().parse(ivyFile);
+        ivyModule.info. at status == "foo"
+        ivyModule.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:changed:1.0", "org.test:Dep2:1.0"]
+    }
+
+    def date(String timestamp) {
+        def format = new SimpleDateFormat("yyyyMMddHHmmss")
+        format.parse(timestamp)
+    }
+
+    def mockDependencyDescriptor(String organisation = "org.test", String moduleName, String revision = "1.0") {
+        DependencyDescriptor dependencyDescriptor = Mock()
+        ModuleRevisionId moduleRevisionId = Mock()
+        1 * moduleRevisionId.organisation >> organisation
+        1 * moduleRevisionId.name >> moduleName
+        1 * moduleRevisionId.revision >> revision
+        1 * dependencyDescriptor.dependencyRevisionId >> moduleRevisionId
+        1 * dependencyDescriptor.dynamicConstraintDependencyRevisionId >> moduleRevisionId
+        1 * dependencyDescriptor.moduleConfigurations >> ["default"]
+        1 * dependencyDescriptor.getDependencyConfigurations("default") >> ["compile, archives"]
+        1 * dependencyDescriptor.allDependencyArtifacts >> []
+        1 * dependencyDescriptor.allIncludeRules >> []
+        1 * dependencyDescriptor.allExcludeRules >> []
+        dependencyDescriptor
+    }
+
+    def mockArtifact() {
+        Artifact artifact = Mock()
+        1 * artifact.name >> "testartifact"
+        1 * artifact.ext >> "jar"
+        1 * artifact.type >> "jar"
+        1 * md.getArtifacts("archives") >> [artifact]
+        1 * md.getArtifacts("compile") >> []
+        1 * md.getArtifacts("runtime") >> []
+        artifact
+    }
+
+    def mockConfiguration(String configurationName, List extended = []) {
+        Configuration configuration = Mock()
+        1 * configuration.name >> configurationName
+        1 * configuration.description >> "just another test configuration"
+        1 * configuration.extends >> extended
+        1 * configuration.visibility >> Configuration.Visibility.PUBLIC
+        configuration
+    }
+}
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
index 3c8f169..3385827 100644
--- 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
@@ -29,10 +29,12 @@ class ResolvedArtifactFactoryTest extends Specification {
     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")
 
+        given:
+        artifact.qualifiedExtraAttributes >> [:]
+
         when:
         ResolvedArtifact resolvedArtifact = factory.create(resolvedDependency, artifact, artifactResolver)
 
@@ -46,8 +48,7 @@ class ResolvedArtifactFactoryTest extends Specification {
         1 * lockingManager.useCache(!null, !null) >> {String displayName, Factory<?> action ->
             return action.create()
         }
-        1 * artifactResolver.resolve(artifact) >> artifactResolveResult
-        _ * artifactResolveResult.file >> file
+        1 * artifactResolver.resolve(artifact, _) >> { args -> args[1].resolved(file, null) }
         0 * _._
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
new file mode 100644
index 0000000..8748741
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.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.artifacts.ivyservice;
+
+
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.DependencySet
+import org.gradle.api.artifacts.ResolvedConfiguration
+import org.gradle.api.artifacts.result.ResolutionResult
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.CachingDependencyResolveContext
+import org.gradle.api.internal.artifacts.ResolverResults
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import spock.lang.Specification
+
+public class SelfResolvingDependencyResolverTest extends Specification {
+
+    private delegate = Mock(ArtifactDependencyResolver)
+    private resolvedConfiguration = Mock(ResolvedConfiguration)
+    private configuration = Mock(ConfigurationInternal)
+    private dependencies = Mock(DependencySet)
+
+    private resolver = new SelfResolvingDependencyResolver(delegate);
+
+    void "returns correct resolved configuration"() {
+        given:
+        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        configuration.getAllDependencies() >> dependencies
+        configuration.isTransitive() >> true
+
+        when:
+        def results = resolver.resolve(configuration)
+
+        then:
+        def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
+        conf.resolvedConfiguration == resolvedConfiguration
+        conf.selfResolvingFilesProvider
+        conf.selfResolvingFilesProvider.resolveContext.transitive
+        conf.selfResolvingFilesProvider.dependencies == dependencies
+    }
+
+    void "uses configuration transitive setting"() {
+        given:
+        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        configuration.getAllDependencies() >> dependencies
+        configuration.isTransitive() >> false
+
+        when:
+        def results = resolver.resolve(configuration)
+
+        then:
+        def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
+        !conf.selfResolvingFilesProvider.resolveContext.transitive
+    }
+
+    void "delegates to provided resolved configuration"() {
+        given:
+        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        configuration.getAllDependencies() >> dependencies
+        configuration.isTransitive() >> true
+
+        when:
+        def results = resolver.resolve(configuration)
+        results.resolvedConfiguration.getFirstLevelModuleDependencies(Specs.satisfyAll())
+        results.resolvedConfiguration.getResolvedArtifacts()
+        results.resolvedConfiguration.hasError()
+        results.resolvedConfiguration.rethrowFailure()
+        results.resolvedConfiguration.getLenientConfiguration()
+
+        then:
+        1 * resolvedConfiguration.getFirstLevelModuleDependencies(Specs.satisfyAll())
+        1 * resolvedConfiguration.getResolvedArtifacts()
+        1 * resolvedConfiguration.hasError()
+        1 * resolvedConfiguration.rethrowFailure()
+        1 * resolvedConfiguration.getLenientConfiguration()
+    }
+
+    void "knows how to extract self resolving files"() {
+        given:
+        def resolvedFiles = Mock(FileCollection)
+        def resolveContext = Mock(CachingDependencyResolveContext)
+        def fooDep = new DefaultExternalModuleDependency("org", "foo", "1.0")
+        Set<Dependency> dependencies = [fooDep, new DefaultExternalModuleDependency("org", "bar", "1.0")]
+
+        def provider = new SelfResolvingDependencyResolver.SelfResolvingFilesProvider(resolveContext, dependencies)
+
+        when:
+        def files = provider.getFiles({ it.name == 'foo' } as Spec)
+
+        then:
+        1 * resolveContext.add(fooDep)
+        1 * resolveContext.resolve() >> resolvedFiles
+        1 * resolvedFiles.getFiles() >> [new File('foo.jar')]
+        0 * _._
+
+        files*.name == ['foo.jar']
+    }
+
+    void "aggregates files with self resolving files first"() {
+        given:
+        def provider = Mock(SelfResolvingDependencyResolver.SelfResolvingFilesProvider) {
+            getFiles(Specs.satisfyAll()) >> [new File("foo.jar")]
+        }
+        resolvedConfiguration.getFiles(Specs.satisfyAll()) >> new HashSet<File>([new File("bar.jar")])
+
+        def conf = new SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration(resolvedConfiguration, provider)
+
+        when:
+        def files = conf.getFiles(Specs.satisfyAll())
+
+        then:
+        files*.name == ['foo.jar', 'bar.jar']
+    }
+}
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
deleted file mode 100644
index f4be57d..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
+++ /dev/null
@@ -1,177 +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.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/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
new file mode 100644
index 0000000..d7e2e30
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.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.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.DefaultModule
+import org.gradle.api.internal.artifacts.ResolverResults
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.specs.Specs
+import spock.lang.Specification
+
+class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specification {
+
+    final ArtifactDependencyResolver delegate = Mock()
+    final ConfigurationInternal configuration = Mock()
+    final DependencySet dependencies = Mock()
+
+    final ShortcircuitEmptyConfigsArtifactDependencyResolver dependencyResolver = new ShortcircuitEmptyConfigsArtifactDependencyResolver(delegate);
+
+    def "returns empty config when no dependencies"() {
+        given:
+        dependencies.isEmpty() >> true
+        configuration.getAllDependencies() >> dependencies
+        configuration.getModule() >> new DefaultModule("org", "foo", "1.0")
+
+        when:
+        ResolverResults results = dependencyResolver.resolve(configuration);
+        ResolvedConfiguration resolvedConfig = results.resolvedConfiguration
+
+        then:
+        !resolvedConfig.hasError()
+        resolvedConfig.rethrowFailure();
+
+        resolvedConfig.getFiles(Specs.<Dependency>satisfyAll()).isEmpty()
+        resolvedConfig.getFirstLevelModuleDependencies().isEmpty()
+        resolvedConfig.getResolvedArtifacts().isEmpty()
+    }
+
+    def "delegates to backing service"() {
+        given:
+        def resultsDummy = Mock(ResolverResults)
+
+        dependencies.isEmpty() >> false
+        configuration.getAllDependencies() >> dependencies
+        delegate.resolve(configuration) >> resultsDummy
+
+        when:
+        def out = dependencyResolver.resolve(configuration)
+
+        then:
+        out == resultsDummy
+    }
+}
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
deleted file mode 100644
index cc17793..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverTest.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;
-
-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
index 5d0e3b3..19ebe4d 100644
--- 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
@@ -46,10 +46,11 @@ class VersionForcingDependencyToModuleResolverTest extends Specification {
         def dep = dependency('group', 'module')
 
         when:
-        def result = resolver.resolve(dep)
+        ForcedModuleVersionIdResolveResult result = resolver.resolve(dep)
 
         then:
-        result == resolvedVersion
+        result.result == resolvedVersion
+        result.selectionReason == ModuleVersionIdResolveResult.IdSelectionReason.forced
 
         and:
         1 * dep.clone(forced) >> modified
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
index f364677..36d85e2 100644
--- 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
@@ -20,66 +20,61 @@ 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.BuildableModuleVersionResolveResult
 import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
 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()
+        BuildableModuleVersionResolveResult result = Mock()
+
+        given:
+        _ * dependencyDescriptor.targetModule >> module
 
         when:
-        def resolveResult = resolver.resolve(dependencyDescriptor)
+        resolver.resolve(dependencyDescriptor, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor) >> resolvedModule
-        _ * dependencyDescriptor.targetModule >> module
-
-        and:
-        resolveResult.descriptor == module
-        resolveResult.failure == null
-        resolveResult.id == module.moduleRevisionId
+        1 * target.resolve(dependencyDescriptor, result)
+        1 * result.setMetaData(module.moduleRevisionId, module)
+        _ * result.failure >> null
+        0 * result._
     }
 
     def "does not replace meta-data for unknown module version"() {
         DependencyDescriptor dependencyDescriptor = Mock()
-        
+        BuildableModuleVersionResolveResult result = Mock()
+
         when:
-        def resolveResult = resolver.resolve(dependencyDescriptor)
+        resolver.resolve(dependencyDescriptor, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor) >> resolvedModule
-
-        and:
-        resolveResult == resolvedModule
+        1 * target.resolve(dependencyDescriptor, result)
+        _ * result.failure >> null
+        0 * result._
     }
 
     def "does not replace meta-data for broken module version"() {
         ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
-
-        given:
-        resolvedModule.failure >> new ModuleVersionResolveException("broken")
+        BuildableModuleVersionResolveResult result = Mock()
 
         when:
-        def resolveResult = resolver.resolve(dependencyDescriptor)
+        resolver.resolve(dependencyDescriptor, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor) >> resolvedModule
-
-        and:
-        resolveResult == resolvedModule
+        1 * target.resolve(dependencyDescriptor, result)
+        _ * result.failure >> new ModuleVersionResolveException("broken")
+        0 * result._
     }
 }
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
index f92cce2..868a2fc 100644
--- 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
@@ -17,22 +17,21 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
 import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.id.ArtifactId
 import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy
-
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult
 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.ivy.ArtifactAtRepositoryKey
 import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
 import org.gradle.internal.TrueTimeProvider
+import spock.lang.Specification
+import spock.lang.Unroll
 
 class CachingModuleVersionRepositoryTest extends Specification {
 
@@ -47,7 +46,8 @@ class CachingModuleVersionRepositoryTest extends Specification {
     @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)
+        File file = new File("local")
+        BuildableArtifactResolveResult result = Mock()
         Artifact artifact = Mock()
         ArtifactRevisionId id = arid()
         ArtifactAtRepositoryKey atRepositoryKey = new ArtifactAtRepositoryKey(realRepo, id)
@@ -56,14 +56,16 @@ class CachingModuleVersionRepositoryTest extends Specification {
         and:
         _ * realRepo.isLocal() >> false
         _ * artifactAtRepositoryCache.lookup(atRepositoryKey) >> null
-        _ * realRepo.download(artifact) >> downloadedArtifact
+        _ * realRepo.resolve(artifact, result)
+        _ * result.file >> file
+        _ * result.externalResourceMetaData >> externalResourceMetaData
         _ * artifact.getId() >> id
 
         when:
-        repo.download(artifact)
+        repo.resolve(artifact, result)
         
         then:
-        1 * artifactAtRepositoryCache.store(atRepositoryKey, downloadedArtifact.localFile, externalResourceMetaData)
+        1 * artifactAtRepositoryCache.store(atRepositoryKey, file, externalResourceMetaData)
         
         where:
         lastModified << [new Date(), null]
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
new file mode 100644
index 0000000..70c6fba
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.artifacts.ivyservice.ModuleVersionResolveException
+import spock.lang.Specification
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+
+class DefaultBuildableModuleVersionDescriptorTest extends Specification {
+    final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor()
+
+    def "has unknown state by default"() {
+        expect:
+        descriptor.state == BuildableModuleVersionDescriptor.State.Unknown
+    }
+
+    def "can mark as missing"() {
+        when:
+        descriptor.missing()
+
+        then:
+        descriptor.state == BuildableModuleVersionDescriptor.State.Missing
+        descriptor.failure == null
+    }
+
+    def "can mark as probably missing"() {
+        when:
+        descriptor.probablyMissing()
+
+        then:
+        descriptor.state == BuildableModuleVersionDescriptor.State.ProbablyMissing
+        descriptor.failure == null
+    }
+
+    def "can mark as failed"() {
+        def failure = new ModuleVersionResolveException("broken")
+
+        when:
+        descriptor.failed(failure)
+
+        then:
+        descriptor.state == BuildableModuleVersionDescriptor.State.Failed
+        descriptor.failure == failure
+    }
+
+    def "can mark as resolved"() {
+        def moduleDescriptor = Mock(ModuleDescriptor)
+
+        when:
+        descriptor.resolved(moduleDescriptor, true)
+
+        then:
+        descriptor.state == BuildableModuleVersionDescriptor.State.Resolved
+        descriptor.failure == null
+        descriptor.descriptor == moduleDescriptor
+        descriptor.changing
+    }
+
+    def "cannot get result when not resolved"() {
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+
+        when:
+        descriptor.failure
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get result when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException("broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.descriptor
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get result when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get result when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
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
index 34f5a64..eb9b3fd 100644
--- 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
@@ -19,7 +19,7 @@ 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
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver
 
 public class DependencyResolverIdentifierTest extends Specification {
     def "uses dependency resolver name"() {
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
index 4fbbd89..aee0737 100644
--- 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
@@ -20,17 +20,17 @@ 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.*
+import spock.lang.Specification
 
 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()
+        def module = module()
 
         when:
         def idResolveResult = resolver.resolve(dependency)
@@ -45,8 +45,10 @@ class LazyDependencyToModuleResolverTest extends Specification {
         def moduleResolveResult = idResolveResult.resolve()
 
         then:
-        1 * target.resolve(dependency) >> resolvedModule
-        1 * resolvedModule.descriptor >> module()
+        moduleResolveResult.id == module.moduleRevisionId
+        moduleResolveResult.descriptor == module
+
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, Mock(ArtifactResolver))}
         0 * target._
     }
 
@@ -65,13 +67,11 @@ class LazyDependencyToModuleResolverTest extends Specification {
         id == module.moduleRevisionId
 
         and:
-        1 * target.resolve(dependency) >> resolvedModule
-        _ * resolvedModule.id >> module.moduleRevisionId
-        _ * resolvedModule.descriptor >> module
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, Mock(ArtifactResolver))}
         0 * target._
 
         when:
-        def moduleResolveResult = idResolveResult.resolve()
+        idResolveResult.resolve()
 
         then:
         0 * target._
@@ -79,18 +79,18 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
     def "does not resolve module more than once"() {
         def dependency = dependency()
+        def module = module()
 
         when:
         def idResolveResult = resolver.resolve(dependency)
         idResolveResult.resolve()
 
         then:
-        1 * target.resolve(dependency) >> resolvedModule
-        1 * resolvedModule.descriptor >> module()
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, Mock(ArtifactResolver))}
         0 * target._
 
         when:
-        def moduleResolveResult = idResolveResult.resolve()
+        idResolveResult.resolve()
 
         then:
         0 * target._
@@ -116,8 +116,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         resolveResult.failure.is(failure)
 
         and:
-        1 * target.resolve(dependency) >> resolvedModule
-        _ * resolvedModule.failure >> failure
+        1 * target.resolve(dependency, _) >> { args -> args[1].failed(failure)}
         0 * target._
 
         when:
@@ -142,8 +141,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         resolveResult.failure.message == "Could not find group:group, module:module, version:1.0."
 
         and:
-        1 * target.resolve(dependency) >> resolvedModule
-        _ * resolvedModule.failure >> new ModuleVersionNotFoundException("broken")
+        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException("broken"))}
     }
 
     def "collects and wraps unexpected module resolve failure"() {
@@ -158,7 +156,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         resolveResult.failure.message == "Could not resolve group:group, module:module, version:1.0."
 
         and:
-        1 * target.resolve(dependency) >> { throw failure }
+        1 * target.resolve(dependency, _) >> { throw failure }
     }
 
     def "collects and wraps module not found for missing dynamic version"() {
@@ -175,8 +173,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         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")
+        1 * target.resolve(dependency, _) >> { args -> args[1].failed(new ModuleVersionNotFoundException("missing"))}
 
         when:
         idResolveResult.id
@@ -199,59 +196,51 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
     def "can resolve artifact for a module version"() {
         def dependency = dependency()
+        def module = module()
         def artifact = artifact()
         ArtifactResolver targetResolver = Mock()
-        ArtifactResolveResult resolvedArtifact = Mock()
+        BuildableArtifactResolveResult result = Mock()
 
         when:
         def resolveResult = resolver.resolve(dependency).resolve()
 
         then:
-        1 * target.resolve(dependency) >> resolvedModule
-        _ * resolvedModule.descriptor >> module()
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, targetResolver)}
 
         when:
-        def artifactResult = resolveResult.artifactResolver.resolve(artifact)
+        resolveResult.artifactResolver.resolve(artifact, result)
 
         then:
-        artifactResult == resolvedArtifact
-
-        and:
-        _ * resolvedModule.artifactResolver >> targetResolver
-        1 * targetResolver.resolve(artifact) >> resolvedArtifact
+        1 * targetResolver.resolve(artifact, result)
+        0 * targetResolver._
+        0 * target._
     }
     
     def "wraps unexpected failure to resolve artifact"() {
         def dependency = dependency()
         def artifact = artifact()
+        def module = module()
         ArtifactResolver targetResolver = Mock()
+        BuildableArtifactResolveResult result = Mock()
         def failure = new RuntimeException("broken")
 
         when:
         def resolveResult = resolver.resolve(dependency).resolve()
 
         then:
-        1 * target.resolve(dependency) >> resolvedModule
-        _ * resolvedModule.descriptor >> module()
+        1 * target.resolve(dependency, _) >> { args -> args[1].resolved(module.moduleRevisionId, module, targetResolver)}
 
         when:
-        def artifactResult = resolveResult.artifactResolver.resolve(artifact)
+        resolveResult.artifactResolver.resolve(artifact, result)
 
         then:
-        artifactResult.failure instanceof ArtifactResolveException
-        artifactResult.failure.message == "Could not download artifact 'group:module:1.0 at zip'"
-        artifactResult.failure.cause == failure
+        1 * result.failed(_) >> { ArtifactResolveException e ->
+            assert e.message == "Could not download artifact 'group:module:1.0 at zip'"
+            assert e.cause == failure
+        }
 
         and:
-        _ * resolvedModule.artifactResolver >> targetResolver
-        _ * targetResolver.resolve(artifact) >> { throw failure }
-
-        when:
-        artifactResult.file
-
-        then:
-        ArtifactResolveException e = thrown()
-        e.is(artifactResult.failure)
+        _ * targetResolver.resolve(artifact, result) >> { throw failure }
     }
 
     def module() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
new file mode 100644
index 0000000..a5f07f8
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 spock.lang.Specification
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
+import org.apache.ivy.plugins.resolver.ResolverSettings
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.plugins.version.VersionMatcher
+import org.apache.ivy.plugins.latest.LatestRevisionStrategy
+
+class UserResolverChainTest extends Specification {
+    final UserResolverChain resolver = new UserResolverChain()
+    final ModuleRevisionId dependencyId = Stub()
+    final DependencyDescriptor dependency = Stub()
+    final ModuleDescriptor descriptor = descriptor("1.2")
+    final ModuleRevisionId resolvedId = descriptor.resolvedModuleRevisionId
+    final BuildableModuleVersionResolveResult result = Mock()
+    final VersionMatcher matcher = Stub()
+
+    def setup() {
+        dependency.dependencyRevisionId >> dependencyId
+        def settings = Stub(ResolverSettings)
+        _ * settings.versionMatcher >> matcher
+        _ * settings.defaultLatestStrategy >> new LatestRevisionStrategy();
+        resolver.settings = settings
+    }
+
+    def "uses local dependency when available"() {
+        given:
+        def repo = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo)
+
+        and:
+        _ * repo.name >> "repo"
+        0 * repo._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is unknown"() {
+        given:
+        def repo = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo.getLocalDependency(dependency, _)
+        1 * repo.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo)
+
+        and:
+        _ * repo.name >> "repo"
+        0 * repo._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is probably missing"() {
+        given:
+        def repo = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo)
+
+        and:
+        _ * repo.name >> "repo"
+        0 * repo._
+        0 * result._
+    }
+
+    def "fails with not found when local dependency is marked as missing"() {
+        given:
+        def repo = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * result.notFound(dependencyId)
+
+        and:
+        _ * repo.name >> "repo"
+        0 * repo._
+        0 * result._
+    }
+
+    def "fails with not found when local and remote dependency marked as missing"() {
+        given:
+        def repo = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo.getDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * result.notFound(dependencyId)
+
+        and:
+        _ * repo.name >> "repo"
+        0 * repo._
+        0 * result._
+    }
+
+    def "searches all repositories for a dynamic version"() {
+        given:
+        _ * matcher.isDynamic(_) >> true
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        def repo3 = Mock(LocalAwareModuleVersionRepository)
+        def version2 = descriptor("1.2")
+        resolver.add(repo1)
+        resolver.add(repo2)
+        resolver.add(repo3)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolve(descriptor("1.1"), true)
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(version2, true)
+        }
+        1 * repo3.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor("1.0"), true)
+        }
+        1 * result.resolved(version2.resolvedModuleRevisionId, version2, repo2)
+
+        and:
+        _ * repo1.name >> "repo1"
+        _ * repo2.name >> "repo2"
+        _ * repo3.name >> "repo3"
+        0 * repo1._
+        0 * repo2._
+        0 * repo3._
+        0 * result._
+    }
+
+    def "stops on first available local dependency for static version"() {
+        given:
+        _ * matcher.isDynamic(_) >> false
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        def repo3 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+        resolver.add(repo3)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo1)
+
+        and:
+        _ * repo1.name >> "repo1"
+        _ * repo2.name >> "repo2"
+        _ * repo3.name >> "repo3"
+        0 * repo1._
+        0 * repo2._
+        0 * repo3._
+        0 * result._
+    }
+
+    def "uses local dependency when available in one repository and missing from all other repositories"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "uses local dependency when available in one repository and probably missing in all other repositories"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "uses remote dependency when local dependency is unknown for a given repository and probably missing in other repositories"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo2.getLocalDependency(dependency, _)
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is probably missing in all repositories"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo1.getDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "does not attempt to resolve remote dependency when local dependency is missing"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "attempts to find remote dependency when local dependency is missing or unknown in all repositories"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            result.probablyMissing()
+        }
+        1 * repo2.getLocalDependency(dependency, _)
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * repo1.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo1)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "ignores failure to resolve local dependency when available in another repository"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            throw new RuntimeException("broken")
+        }
+        1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "ignores failure to resolve remote dependency when available in another repository"() {
+        given:
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _)
+        1 * repo1.getDependency(dependency, _) >> { dep, result ->
+            throw new RuntimeException("broken")
+        }
+        1 * repo2.getLocalDependency(dependency, _)
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.resolved(descriptor, true)
+        }
+        1 * result.resolved(resolvedId, descriptor, repo2)
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "rethrows failure to resolve local dependency when not available in any repository"() {
+        given:
+        def failure = new RuntimeException("broken")
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
+            throw failure
+        }
+        1 * repo2.getLocalDependency(dependency, _)
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * result.failed({it.cause == failure})
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def "rethrows failure to resolve remote dependency when not available in any repository"() {
+        given:
+        def failure = new RuntimeException("broken")
+        def repo1 = Mock(LocalAwareModuleVersionRepository)
+        def repo2 = Mock(LocalAwareModuleVersionRepository)
+        resolver.add(repo1)
+        resolver.add(repo2)
+
+        when:
+        resolver.resolve(dependency, result)
+
+        then:
+        1 * repo1.getLocalDependency(dependency, _)
+        1 * repo1.getDependency(dependency, _) >> { dep, result ->
+            throw failure
+        }
+        1 * repo2.getLocalDependency(dependency, _)
+        1 * repo2.getDependency(dependency, _) >> { dep, result ->
+            result.missing()
+        }
+        1 * result.failed({it.cause == failure})
+
+        and:
+        _ * repo1.name >> "repo"
+        _ * repo2.name >> "repo"
+        0 * repo1._
+        0 * repo2._
+        0 * result._
+    }
+
+    def descriptor(def version) {
+        def descriptor = Stub(ModuleDescriptor)
+        descriptor.resolvedModuleRevisionId >> ModuleRevisionId.newInstance("org", "module", version)
+        return descriptor
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.groovy
new file mode 100644
index 0000000..2d4d058
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStoreTest.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.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.xml.XmlModuleDescriptorParser
+import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository
+import org.gradle.api.internal.filestore.FileStoreEntry
+import org.gradle.api.internal.filestore.PathKeyFileStore
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class ModuleDescriptorStoreTest extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder
+    ModuleDescriptorStore store
+    PathKeyFileStore pathKeyFileStore = Mock()
+    ModuleRevisionId moduleRevisionId = Mock()
+    ModuleVersionRepository repository = Mock()
+    FileStoreEntry fileStoreEntry = Mock()
+    ModuleDescriptor moduleDescriptor = Mock()
+    IvyModuleDescriptorWriter ivyModuleDescriptorWriter = Mock()
+    XmlModuleDescriptorParser xmlModuleDescriptorParser = Mock()
+
+    def setup() {
+        store = new ModuleDescriptorStore(pathKeyFileStore, ivyModuleDescriptorWriter, xmlModuleDescriptorParser);
+        _ * repository.getId() >> "repositoryId"
+        _ * moduleRevisionId.getOrganisation() >> "org.test"
+        _ * moduleRevisionId.getName() >> "testArtifact"
+        _ * moduleRevisionId.getRevision() >> "1.0"
+        _ * moduleDescriptor.getModuleRevisionId() >> moduleRevisionId
+    }
+
+    def "getModuleDescriptorFile returns null for not cached descriptors"() {
+        when:
+        pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml") >> null
+        then:
+        null == store.getModuleDescriptor(repository, moduleRevisionId)
+    }
+
+    def "getModuleDescriptorFile uses PathKeyFileStore to get file"() {
+        when:
+        store.getModuleDescriptor(repository, moduleRevisionId);
+        then:
+        1 * pathKeyFileStore.get("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml") >> null
+    }
+
+    def "putModuleDescriptor uses PathKeyFileStore to write file"() {
+        setup:
+        File descriptorFile = temporaryFolder.createFile("fileStoreEntry")
+        when:
+        store.putModuleDescriptor(repository, moduleDescriptor);
+        then:
+        1 * pathKeyFileStore.add("module-metadata/org.test/testArtifact/1.0/repositoryId.ivy.xml", _) >> {path, action ->
+            action.execute(descriptorFile); fileStoreEntry
+        };
+        1 * ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorFile)
+    }
+}
\ 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
index a27ac73..9dc6f03 100644
--- 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
@@ -17,40 +17,41 @@ 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.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
 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 ModuleRevisionId moduleRevisionId = Mock()
     final DependencyToModuleResolver target = Mock()
     final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target)
     
     def "resolves project dependency"() {
         ModuleDescriptor moduleDescriptor = Mock()
+        BuildableModuleVersionResolveResult result = Mock()
 
         when:
-        def moduleResolver = resolver.resolve(dependencyDescriptor)
+        resolver.resolve(dependencyDescriptor, result)
 
         then:
-        moduleResolver.descriptor == moduleDescriptor
-
-        and:
         1 * registry.findProject(dependencyDescriptor) >> moduleDescriptor
+        _ * moduleDescriptor.moduleRevisionId >> moduleRevisionId
+        1 * result.resolved(moduleRevisionId, moduleDescriptor, _)
+        0 * result._
     }
 
     def "delegates to backing resolver for non-project dependency"() {
-        ModuleVersionResolveResult resolvedModule = Mock()
+        BuildableModuleVersionResolveResult result = Mock()
 
         when:
-        def moduleResolver = resolver.resolve(dependencyDescriptor)
+        resolver.resolve(dependencyDescriptor, result)
 
         then:
-        moduleResolver == resolvedModule
-
-        and:
         1 * registry.findProject(dependencyDescriptor) >> null
-        1 * target.resolve(dependencyDescriptor) >> resolvedModule
+        1 * target.resolve(dependencyDescriptor, result)
+        0 * result._
     }
 }
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
index d3d6f06..a553d0d 100644
--- 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
@@ -24,15 +24,19 @@ import org.apache.ivy.core.resolve.ResolveOptions
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 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.internal.artifacts.ivyservice.resolveengine.result.ResolvedConfigurationListener
 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.*
 
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+
 class DependencyGraphBuilderTest extends Specification {
     final ModuleDescriptorConverter moduleDescriptorConverter = Mock()
     final ResolvedArtifactFactory resolvedArtifactFactory = Mock()
@@ -41,6 +45,7 @@ class DependencyGraphBuilderTest extends Specification {
     final ResolveData resolveData = new ResolveData(resolveEngine, new ResolveOptions())
     final ModuleConflictResolver conflictResolver = Mock()
     final DependencyToModuleVersionIdResolver dependencyResolver = Mock()
+    final ResolvedConfigurationListener listener = Mock()
     final DefaultModuleDescriptor root = revision('root')
     final DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, dependencyResolver, conflictResolver)
 
@@ -64,13 +69,35 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
         modules(result) == ids(a, b, c)
     }
 
+    def "correctly notifies the resolved configuration listener"() {
+        given:
+        def a = revision("a")
+        def b = revision("b")
+        def c = revision("c")
+        def d = revision("d")
+        traverses root, a
+        traverses root, b
+        traverses a, c
+        traversesMissing a, d
+
+        when:
+        builder.resolve(configuration, resolveData, listener)
+
+        then:
+        1 * listener.start(newId("group", "root", "1.0"))
+        then:
+        1 * listener.resolvedConfiguration({ it.name == 'root' }, { it*.requested.name == ['a', 'b'] } )
+        then:
+        1 * listener.resolvedConfiguration({ it.name == 'a' },    { it*.requested.name == ['c', 'd'] && it*.failure.count { it != null } == 1 } )
+    }
+
     def "does not resolve a given dynamic module selector more than once"() {
         given:
         def a = revision("a")
@@ -85,7 +112,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve c, d
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -108,7 +135,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve evicted, e
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -138,7 +165,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses selected, e
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -168,7 +195,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses selected, e
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -194,7 +221,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve evicted, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -223,7 +250,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -253,7 +280,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve selectedB, evictedA2
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -289,7 +316,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses e, selected // conflict is deeper than 'b', to ensure 'b' has been visited
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -319,7 +346,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses e, selected // conflict is deeper than 'b', to ensure 'b' has been visited
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -342,7 +369,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -357,7 +384,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve root, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -382,7 +409,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve d, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -403,7 +430,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses d, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -422,7 +449,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve c, a
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -445,7 +472,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve d, e
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -464,7 +491,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses a, b
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -482,7 +509,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -502,12 +529,12 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -532,12 +559,12 @@ class DependencyGraphBuilderTest extends Specification {
         brokenSelector a, 'unknown'
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'unknown', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'unknown', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -561,12 +588,12 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -591,12 +618,12 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionNotFoundException
 
         when:
@@ -621,12 +648,12 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -650,12 +677,12 @@ class DependencyGraphBuilderTest extends Specification {
         traversesMissing b, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
+        unresolved.selector == new DefaultModuleVersionSelector('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -684,7 +711,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve selected, c
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
 
         then:
         1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
@@ -714,7 +741,7 @@ class DependencyGraphBuilderTest extends Specification {
         traversesMissing b, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -738,7 +765,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses b, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -762,7 +789,7 @@ class DependencyGraphBuilderTest extends Specification {
         traverses c, selected
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
@@ -784,7 +811,7 @@ class DependencyGraphBuilderTest extends Specification {
         doesNotResolve b, evicted
 
         when:
-        def result = builder.resolve(configuration, resolveData)
+        def result = builder.resolve(configuration, resolveData, listener)
         result.rethrowFailure()
 
         then:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy
new file mode 100644
index 0000000..7a5dbd5
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy
@@ -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.artifacts.ivyservice.resolveengine.result
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+
+/**
+ * by Szczepan Faber, created at: 10/1/12
+ */
+class CachingDependencyResultFactoryTest extends Specification {
+
+    CachingDependencyResultFactory factory = new CachingDependencyResultFactory()
+
+    def "creates and caches resolved dependencies"() {
+        def fromModule = newModule('from')
+        def selectedModule = newModule('selected')
+
+        when:
+        def dep = factory.createResolvedDependency(selector('requested'), fromModule, selectedModule)
+        def same = factory.createResolvedDependency(selector('requested'), fromModule, selectedModule)
+
+        def differentRequested = factory.createResolvedDependency(selector('xxx'), fromModule, selectedModule)
+        def differentFrom = factory.createResolvedDependency(selector('requested'), newModule('xxx'), selectedModule)
+        def differentSelected = factory.createResolvedDependency(selector('requested'), fromModule, newModule('xxx'))
+
+        then:
+        dep.is(same)
+        !dep.is(differentFrom)
+        !dep.is(differentRequested)
+        !dep.is(differentSelected)
+    }
+
+    def "creates and caches unresolved dependencies"() {
+        def fromModule = newModule('from')
+
+        when:
+        def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("foo"))
+        def same = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("foo"))
+
+        def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, new RuntimeException("foo"))
+        def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), new RuntimeException("foo"))
+        def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, new RuntimeException("xxx"))
+
+        then:
+        dep.is(same)
+        !dep.is(differentFrom)
+        !dep.is(differentRequested)
+        dep.is(differentFailure) //the same dependency edge cannot have different failures
+    }
+
+    def selector(String group='a', String module='a', String version='1') {
+        newSelector(group, module, version)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
new file mode 100644
index 0000000..9317fad
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ResolutionResultBuilderSpec.groovy
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.result
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import spock.lang.Specification
+import org.gradle.api.artifacts.result.*
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 8/27/12
+ */
+class ResolutionResultBuilderSpec extends Specification {
+
+    def builder = new ResolutionResultBuilder()
+
+    def "builds basic graph"() {
+        given:
+        builder.start(confId("root"))
+        resolvedConf("root", [dep("mid1"), dep("mid2")])
+
+        resolvedConf("mid1", [dep("leaf1"), dep("leaf2")])
+        resolvedConf("mid2", [dep("leaf3"), dep("leaf4")])
+
+        resolvedConf("leaf1", [])
+        resolvedConf("leaf2", [])
+        resolvedConf("leaf3", [])
+        resolvedConf("leaf4", [])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        print(result.root) == """x:root:1
+  x:mid1:1 [root]
+    x:leaf1:1 [mid1]
+    x:leaf2:1 [mid1]
+  x:mid2:1 [root]
+    x:leaf3:1 [mid2]
+    x:leaf4:1 [mid2]
+"""
+    }
+
+    def "graph with multiple dependents"() {
+        given:
+        builder.start(confId("a"))
+        resolvedConf("a", [dep("b1"), dep("b2"), dep("b3")])
+
+        resolvedConf("b1", [dep("b2"), dep("b3")])
+        resolvedConf("b2", [dep("b3")])
+        resolvedConf("b3", [])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        print(result.root) == """x:a:1
+  x:b1:1 [a]
+    x:b2:1 [a,b1]
+      x:b3:1 [a,b1,b2]
+  x:b2:1 [a,b1]
+    x:b3:1 [a,b1,b2]
+  x:b3:1 [a,b1,b2]
+"""
+    }
+
+    def "builds graph with cycles"() {
+        given:
+        builder.start(confId("a"))
+        resolvedConf("a", [dep("b")])
+        resolvedConf("b", [dep("c")])
+        resolvedConf("c", [dep("a")])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        print(result.root) == """x:a:1
+  x:b:1 [a]
+    x:c:1 [b]
+      x:a:1 [c]
+"""
+    }
+
+    def "includes selection reason"() {
+        given:
+        builder.start(confId("a"))
+        resolvedConf("a", [dep("b", null, "b", VersionSelectionReasons.FORCED), dep("c", null, "c", VersionSelectionReasons.CONFLICT_RESOLUTION), dep("d", new RuntimeException("Boo!"))])
+        resolvedConf("b", [])
+        resolvedConf("c", [])
+        resolvedConf("d", [])
+
+        when:
+        def deps = builder.result.root.dependencies
+
+        then:
+        def b = deps.find { it.selected.id.name == 'b' }
+        def c = deps.find { it.selected.id.name == 'c' }
+
+        b.selected.selectionReason.forced
+        c.selected.selectionReason.conflictResolution
+    }
+
+    def "links dependents correctly"() {
+        given:
+        builder.start(confId("a"))
+        resolvedConf("a", [dep("b")])
+        resolvedConf("b", [dep("c")])
+        resolvedConf("c", [dep("a")])
+
+        when:
+        def a = builder.getResult().root
+
+        then:
+        def b  = first(a.dependencies).selected
+        def c  = first(b.dependencies).selected
+        def a2 = first(c.dependencies).selected
+
+        a2.is(a)
+
+        first(b.dependents).is(first(a.dependencies))
+        first(c.dependents).is(first(b.dependencies))
+        first(a.dependents).is(first(c.dependencies))
+
+        first(b.dependents).from.is(a)
+        first(c.dependents).from.is(b)
+        first(a.dependents).from.is(c)
+    }
+
+    ResolvedDependencyResult first(Set<? extends ResolvedDependencyResult> dependencies) {
+        dependencies.iterator().next()
+    }
+
+    def "accumulates and avoids duplicate dependencies"() {
+        given:
+        builder.start(confId("root"))
+        resolvedConf("root", [dep("mid1")])
+
+        resolvedConf("mid1", [dep("leaf1")])
+        resolvedConf("mid1", [dep("leaf1")]) //dupe
+        resolvedConf("mid1", [dep("leaf2")])
+
+        resolvedConf("leaf1", [])
+        resolvedConf("leaf2", [])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        print(result.root) == """x:root:1
+  x:mid1:1 [root]
+    x:leaf1:1 [mid1]
+    x:leaf2:1 [mid1]
+"""
+    }
+
+    def "accumulates and avoids duplicate unresolved dependencies"() {
+        given:
+        builder.start(confId("root"))
+        resolvedConf("root", [dep("mid1")])
+
+        resolvedConf("mid1", [dep("leaf1", new RuntimeException("foo!"))])
+        resolvedConf("mid1", [dep("leaf1", new RuntimeException("bar!"))]) //dupe
+        resolvedConf("mid1", [dep("leaf2", new RuntimeException("baz!"))])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        def mid1 = first(result.root.dependencies)
+        mid1.selected.dependencies.size() == 2
+        mid1.selected.dependencies*.requested.name == ['leaf1', 'leaf2']
+    }
+
+    def "graph includes unresolved deps"() {
+        given:
+        builder.start(confId("a"))
+        resolvedConf("a", [dep("b"), dep("c"), dep("U", new RuntimeException("unresolved!"))])
+        resolvedConf("b", [])
+        resolvedConf("c", [])
+
+        when:
+        def result = builder.getResult()
+
+        then:
+        print(result.root) == """x:a:1
+  x:b:1 [a]
+  x:c:1 [a]
+  x:U:1 - unresolved!
+"""
+    }
+
+    private void resolvedConf(String module, List<InternalDependencyResult> deps) {
+        def moduleVersion = new DummyModuleVersionSelection(selectedId: newId("x", module, "1"), selectionReason: VersionSelectionReasons.REQUESTED)
+        builder.resolvedModuleVersion(moduleVersion)
+        deps.each {
+            if (it.selected) {
+                builder.resolvedModuleVersion(it.selected)
+            }
+        }
+        builder.resolvedConfiguration(confId(module), deps)
+    }
+
+    private InternalDependencyResult dep(String requested, Exception failure = null, String selected = requested, ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
+        def selection = failure != null ? null : new DummyModuleVersionSelection(selectedId: newId("x", selected, "1"), selectionReason: selectionReason)
+        new DummyInternalDependencyResult(requested: newSelector("x", requested, "1"), selected: selection, failure: failure)
+    }
+
+    private ModuleVersionIdentifier confId(String module) {
+        newId("x", module, "1")
+    }
+
+    String print(ResolvedModuleVersionResult root) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(root).append("\n");
+        for (DependencyResult d : root.getDependencies()) {
+            print(d, sb, new HashSet(), "  ");
+        }
+
+        sb.toString();
+    }
+
+    void print(DependencyResult dep, StringBuilder sb, Set visited, String indent) {
+        if (dep instanceof UnresolvedDependencyResult) {
+            sb.append(indent + dep + "\n");
+            return
+        }
+        if (!visited.add(dep.getSelected())) {
+            return
+        }
+        sb.append(indent + dep + " [" + dep.selected.dependents*.from.id.name.join(",") + "]\n");
+        for (ResolvedDependencyResult d : dep.getSelected().getDependencies()) {
+            print(d, sb, visited, "  " + indent);
+        }
+    }
+
+    class DummyModuleVersionSelection implements ModuleVersionSelection{
+        ModuleVersionIdentifier selectedId
+        ModuleVersionSelectionReason selectionReason
+    }
+
+    class DummyInternalDependencyResult implements InternalDependencyResult {
+        ModuleVersionSelector requested
+        ModuleVersionSelection selected
+        Exception failure
+    }
+}
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
index 67608f3..b7affcc 100644
--- 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
@@ -36,7 +36,7 @@ class DefaultLocalMavenRepositoryLocatorTest extends Specification {
 
     def setup() {
         locations = new SimpleMavenFileLocations()
-        locator = new DefaultLocalMavenRepositoryLocator(locations, systemProperties, environmentVariables)
+        locator = new DefaultLocalMavenRepositoryLocator(new DefaultMavenSettingsProvider(locations), systemProperties, environmentVariables)
     }
 
     def "returns default location if no settings file exists"() {
@@ -53,7 +53,7 @@ class DefaultLocalMavenRepositoryLocatorTest extends Specification {
         locator.localMavenRepository
         then:
         def ex = thrown(CannotLocateLocalMavenRepositoryException);
-        ex.message == "Unable to parse local maven settings"
+        ex.message == "Unable to parse local maven settings."
         ex.cause.message.contains(settingsFile.absolutePath)
     }
 
@@ -65,7 +65,7 @@ class DefaultLocalMavenRepositoryLocatorTest extends Specification {
         locator.localMavenRepository
         then:
         def ex = thrown(CannotLocateLocalMavenRepositoryException)
-        ex.message == "Unable to parse local maven settings"
+        ex.message == "Unable to parse local maven settings."
         ex.cause.message.contains(settingsFile.absolutePath)
     }
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy
new file mode 100644
index 0000000..8d01b62
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * 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.repositories
+
+import org.apache.ivy.core.cache.RepositoryCacheManager
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.artifacts.repositories.ArtifactRepository
+import org.gradle.api.internal.artifacts.ArtifactPublisherFactory
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.logging.ProgressLoggerFactory
+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
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+class DefaultBaseRepositoryFactoryTest {
+    static final URI RESOLVER_URL = new URI('http://a.b.c/')
+    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 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 ArtifactPublisherFactory artifactPublisherFactory = context.mock(ArtifactPublisherFactory)
+    final RepositoryCacheManager localCacheManager = context.mock(RepositoryCacheManager)
+    final RepositoryCacheManager downloadingCacheManager = context.mock(RepositoryCacheManager)
+    final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory)
+
+    final DefaultBaseRepositoryFactory factory = new DefaultBaseRepositoryFactory(
+            localMavenRepoLocator, fileResolver, new DirectInstantiator(), transportFactory, locallyAvailableResourceFinder,
+            progressLoggerFactory, localCacheManager, downloadingCacheManager, artifactPublisherFactory
+    )
+
+    @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 }
+        }
+    }
+
+    @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 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 testCreateResolverWithResolverDescription() {
+        DependencyResolver resolver = context.mock(DependencyResolver)
+        
+        ArtifactRepository repository = factory.createRepository(resolver)
+
+        assert repository instanceof FixedResolverArtifactRepository
+        assert repository.resolver == resolver
+    }
+
+    @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() {
+        def repo =factory.createFlatDirRepository()
+        assert repo instanceof DefaultFlatDirArtifactRepository
+    }
+
+    @Test public void testCreateLocalMavenRepo() {
+        File repoDir = new File(".m2/repository")
+
+        context.checking {
+            one(localMavenRepoLocator).getLocalMavenRepository()
+            will(returnValue(repoDir))
+            allowing(fileResolver).resolveUri(repoDir)
+            will(returnValue(repoDir.toURI()))
+        }
+
+        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/repositories/DefaultFlatDirArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
index c43917c..7e923d3 100644
--- 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
@@ -18,22 +18,20 @@ 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)
+    final RepositoryCacheManager localCacheManager = Mock()
+    final DefaultFlatDirArtifactRepository repository = new DefaultFlatDirArtifactRepository(fileResolver, localCacheManager)
 
     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')
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 417b959..60f4b4a 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
@@ -18,13 +18,19 @@ 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.resolver.ExternalResourceResolver
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
 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 org.gradle.api.internal.file.TemporaryFileProvider
 import spock.lang.Specification
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.api.internal.artifacts.ArtifactPublisherFactory
 
 class DefaultIvyArtifactRepositoryTest extends Specification {
     final FileResolver fileResolver = Mock()
@@ -33,8 +39,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
     final RepositoryCacheManager cacheManager = Mock()
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
     final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final ArtifactPublisherFactory artifactPublisherFactory = Mock()
+
     final DefaultIvyArtifactRepository repository = new DefaultIvyArtifactRepository(
-            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, cachedExternalResourceIndex
+            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, artifactPublisherFactory
     )
 
     def "cannot create a resolver for url with unknown scheme"() {
@@ -101,7 +110,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         given:
         fileResolver.resolveUri('repo/') >> fileUri
-        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager)
+        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager, Mock(TemporaryFileProvider))
 
         when:
         def resolver = repository.createResolver()
@@ -232,7 +241,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
     }
 
     private HttpTransport createHttpTransport(String name, PasswordCredentials credentials) {
-        return new HttpTransport(name, credentials, cacheManager)
+        return new HttpTransport(name, credentials, cacheManager, progressLoggerFactory, Mock(TemporaryFileProvider), cachedExternalResourceIndex)
     }
 
 }
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
index 576846d..ca2b132 100644
--- 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
@@ -18,13 +18,16 @@ 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.resolver.MavenResolver
 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 org.gradle.api.internal.file.TemporaryFileProvider
 import spock.lang.Specification
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.logging.ProgressLoggerFactory
 
 class DefaultMavenArtifactRepositoryTest extends Specification {
     final FileResolver resolver = Mock()
@@ -34,14 +37,16 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
     final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
     final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
 
-    final DefaultMavenArtifactRepository repository = new DefaultMavenArtifactRepository(resolver, credentials, transportFactory, locallyAvailableResourceFinder, cachedExternalResourceIndex)
+    final DefaultMavenArtifactRepository repository = new DefaultMavenArtifactRepository(resolver, credentials, transportFactory, locallyAvailableResourceFinder)
+    final ProgressLoggerFactory progressLoggerFactory = Mock();
+
 
     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)
+        transportFactory.createFileTransport('repo') >> new FileTransport('repo', cacheManager, Mock(TemporaryFileProvider))
 
         and:
         repository.name = 'repo'
@@ -105,7 +110,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
     }
 
     private HttpTransport createHttpTransport(String repo, PasswordCredentials credentials) {
-        return new HttpTransport(repo, credentials, cacheManager)
+        return new HttpTransport(repo, credentials, cacheManager, progressLoggerFactory, Mock(TemporaryFileProvider), cachedExternalResourceIndex)
     }
 
     def "fails when no root url specified"() {
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
deleted file mode 100644
index 231ff72..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
+++ /dev/null
@@ -1,152 +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.repositories
-
-import org.apache.ivy.plugins.resolver.DependencyResolver
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.artifacts.dsl.RepositoryHandler
-import org.gradle.api.artifacts.repositories.ArtifactRepository
-import org.gradle.internal.reflect.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.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
-import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-class DefaultResolverFactoryTest {
-    static final URI RESOLVER_URL = new URI('http://a.b.c/')
-    static final String TEST_REPO_NAME = 'reponame'
-    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 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 }
-        }
-    }
-
-    @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 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 testCreateResolverWithResolverDescription() {
-        DependencyResolver resolver = context.mock(DependencyResolver)
-        
-        ArtifactRepository repository = factory.createRepository(resolver)
-
-        assert repository instanceof FixedResolverArtifactRepository
-        assert repository.resolver == resolver
-    }
-
-    @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() {
-        def repo =factory.createFlatDirRepository()
-        assert repo instanceof DefaultFlatDirArtifactRepository
-    }
-
-    @Test public void testCreateLocalMavenRepo() {
-        File repoDir = new File(".m2/repository")
-
-        context.checking {
-            one(localMavenRepoLocator).getLocalMavenRepository()
-            will(returnValue(repoDir))
-            allowing(fileResolver).resolveUri(repoDir)
-            will(returnValue(repoDir.toURI()))
-        }
-
-        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/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
new file mode 100644
index 0000000..e85f894
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManagerTest.groovy
@@ -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.artifacts.repositories.cachemanager
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.plugins.repository.Resource
+import org.apache.ivy.plugins.repository.ResourceDownloader
+import org.apache.ivy.plugins.resolver.util.ResolvedResource
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.api.internal.filestore.FileStore
+import org.gradle.api.internal.filestore.FileStoreEntry
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DownloadingRepositoryCacheManagerTest extends Specification {
+    FileStore<ArtifactRevisionId> fileStore = Mock()
+    CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex = Mock()
+    CacheLockingManager lockingManager = Mock()
+    TemporaryFileProvider tmpFileProvider = Mock()
+    ArtifactRevisionId artifactId = Mock()
+    Artifact artifact = Mock()
+    ResourceDownloader resourceDownloader = Mock()
+    ResolvedResource artifactRef = Mock()
+    Resource resource = Mock();
+    FileStoreEntry fileStoreEntry = Mock()
+    DownloadingRepositoryCacheManager downloadingRepositoryCacheManager = new DownloadingRepositoryCacheManager("TestCacheManager", fileStore, artifactUrlCachedResolutionIndex, tmpFileProvider, lockingManager)
+
+    @Rule TemporaryFolder temporaryFolder;
+
+    void "downloadArtifactFile downloads artifact to temporary file and then moves it into the file store"() {
+        setup:
+
+        def downloadFile = temporaryFolder.createFile("download")
+        def storeFile = temporaryFolder.createFile("store")
+
+        _ * artifact.id >> artifactId
+        _ * artifactRef.resource >> resource
+        _ * fileStoreEntry.file >> storeFile;
+        _ * tmpFileProvider._ >> downloadFile
+
+        when:
+        downloadingRepositoryCacheManager.downloadArtifactFile(artifact, resourceDownloader, artifactRef)
+
+        then:
+        1 * lockingManager.useCache(_, _) >> {name, action ->
+            return action.create()
+        }
+        1 * resourceDownloader.download(artifact, resource, downloadFile)
+        1 * fileStore.move(artifactId, downloadFile) >> {key, action ->
+            return fileStoreEntry
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ChainedVersionListerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ChainedVersionListerTest.groovy
new file mode 100644
index 0000000..2cac12d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ChainedVersionListerTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolver
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.resource.ResourceNotFoundException
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.gradle.api.internal.resource.ResourceException;
+
+class ChainedVersionListerTest extends Specification {
+
+    VersionLister lister1 = Mock()
+    VersionLister lister2 = Mock()
+
+    VersionList versionList1 = Mock()
+    VersionList versionList2 = Mock()
+
+    ResourcePattern pattern = Mock()
+    Artifact artifact = Mock()
+    ModuleRevisionId moduleRevisionId = Mock()
+
+    def chainedVersionLister = new org.gradle.api.internal.artifacts.repositories.resolver.ChainedVersionLister(lister1, lister2)
+
+    def "visit stops listing after first success"() {
+        when:
+        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId);
+
+        then:
+        1 * lister1.getVersionList(moduleRevisionId) >> versionList1
+        1 * lister2.getVersionList(moduleRevisionId) >> versionList2
+
+        when:
+        versionList.visit(pattern, artifact)
+
+        then:
+        1 * versionList1.visit(pattern, artifact)
+        0 * _._
+
+        when:
+        def result = versionList.versionStrings
+
+        then:
+        result == ["1.0", "1.2"] as Set
+
+        and:
+        versionList1.versionStrings >> ["1.0", "1.2"]
+        versionList2.versionStrings >> []
+    }
+
+    @Unroll
+    def "visit ignores #exception.class.simpleName of failed VersionLister"() {
+        given:
+        lister1.getVersionList(moduleRevisionId) >> versionList1
+        lister2.getVersionList(moduleRevisionId) >> versionList2
+
+        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+
+        when:
+        versionList.visit(pattern, artifact)
+
+        then:
+        1 * versionList1.visit(pattern, artifact) >> { throw exception }
+        1 * versionList2.visit(pattern, artifact)
+
+        where:
+        exception << [new ResourceNotFoundException("test resource not found exception"), new ResourceException("test resource exception"), new RuntimeException("broken")]
+    }
+
+    def "visit rethrows ResourceNotFoundException of failed last VersionLister"() {
+        given:
+        def exception = new ResourceNotFoundException("not found")
+        lister1.getVersionList(moduleRevisionId) >> versionList1
+        lister2.getVersionList(moduleRevisionId) >> versionList2
+
+        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+
+        when:
+        versionList.visit(pattern, artifact)
+
+        then:
+        def e = thrown(ResourceNotFoundException)
+        e == exception
+
+        and:
+        1 * versionList1.visit(pattern, artifact) >> { throw new ResourceNotFoundException("ignore me") }
+        1 * versionList2.visit(pattern, artifact) >> { throw exception }
+    }
+
+    def "visit wraps failed last VersionLister"() {
+        given:
+        def exception = new RuntimeException("broken")
+        lister1.getVersionList(moduleRevisionId) >> versionList1
+        lister2.getVersionList(moduleRevisionId) >> versionList2
+
+        VersionList versionList = chainedVersionLister.getVersionList(moduleRevisionId)
+
+        when:
+        versionList.visit(pattern, artifact)
+
+        then:
+        def e = thrown(ResourceException)
+        e.message == "Failed to list versions for ${moduleRevisionId}."
+        e.cause == exception
+
+        and:
+        1 * versionList1.visit(pattern, artifact) >> { throw new ResourceNotFoundException("ignore me") }
+        1 * versionList2.visit(pattern, artifact) >> { throw exception }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResourcePatternTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResourcePatternTest.groovy
new file mode 100644
index 0000000..9b503db
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/IvyResourcePatternTest.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.api.internal.artifacts.repositories.resolver
+
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import spock.lang.Specification
+
+class IvyResourcePatternTest extends Specification {
+    def "substitutes artifact attributes into pattern"() {
+        def pattern = new IvyResourcePattern("prefix/[organisation]-[module]/[revision]/[type]s/[revision]/[artifact].[ext]")
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+        def artifact3 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance(null, "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toPath(artifact1) == 'prefix/group-projectA/1.2/ivys/1.2/ivy.xml'
+        pattern.toPath(artifact2) == 'prefix/org.group-projectA/1.2/ivys/1.2/ivy.xml'
+        pattern.toPath(artifact3) == 'prefix/[organisation]-projectA/1.2/ivys/1.2/ivy.xml'
+    }
+
+    def "substitutes artifact attributes without revision into pattern"() {
+        def pattern = new IvyResourcePattern("prefix/[organisation]-[module]/[revision]/[type]s/[revision]/[artifact].[ext]")
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toPathWithoutRevision(artifact1) == 'prefix/group-projectA/[revision]/ivys/[revision]/ivy.xml'
+        pattern.toPathWithoutRevision(artifact2) == 'prefix/org.group-projectA/[revision]/ivys/[revision]/ivy.xml'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.groovy
new file mode 100644
index 0000000..e3f0b53
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/M2ResourcePatternTest.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.api.internal.artifacts.repositories.resolver
+
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import spock.lang.Specification
+
+class M2ResourcePatternTest extends Specification {
+    def "substitutes artifact attributes into pattern"() {
+        def pattern = new M2ResourcePattern("prefix/[organisation]/[module]/[revision]/[type]s/[revision]/[artifact].[ext]")
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+        def artifact3 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance(null, "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toPath(artifact1) == 'prefix/group/projectA/1.2/ivys/1.2/ivy.xml'
+        pattern.toPath(artifact2) == 'prefix/org/group/projectA/1.2/ivys/1.2/ivy.xml'
+        pattern.toPath(artifact3) == 'prefix/[organisation]/projectA/1.2/ivys/1.2/ivy.xml'
+    }
+
+    def "substitutes artifact attributes without version into pattern"() {
+        def pattern = new M2ResourcePattern("prefix/[organisation]/[module]/[revision]/[type]s/[revision]/[artifact].[ext]")
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+        def artifact3 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance(null, "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toPathWithoutRevision(artifact1) == 'prefix/group/projectA/[revision]/ivys/[revision]/ivy.xml'
+        pattern.toPathWithoutRevision(artifact2) == 'prefix/org/group/projectA/[revision]/ivys/[revision]/ivy.xml'
+        pattern.toPathWithoutRevision(artifact3) == 'prefix/[organisation]/projectA/[revision]/ivys/[revision]/ivy.xml'
+    }
+
+    def "can build module path"() {
+        def pattern = new M2ResourcePattern("prefix/" + MavenPattern.M2_PATTERN)
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toModulePath(artifact1) == 'prefix/group/projectA'
+        pattern.toModulePath(artifact2) == 'prefix/org/group/projectA'
+    }
+
+    def "can build module version path"() {
+        def pattern = new M2ResourcePattern("prefix/" + MavenPattern.M2_PATTERN)
+        def artifact1 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date())
+        def artifact2 = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org.group", "projectA", "1.2"), new Date())
+
+        expect:
+        pattern.toModuleVersionPath(artifact1) == 'prefix/group/projectA/1.2'
+        pattern.toModuleVersionPath(artifact2) == 'prefix/org/group/projectA/1.2'
+    }
+
+    def "throws UnsupportedOperationException for non M2 compatible pattern"() {
+        def pattern = new M2ResourcePattern("/non/m2/pattern")
+
+        when:
+        pattern.toModulePath(DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("group", "projectA", "1.2"), new Date()))
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolverTest.groovy
new file mode 100644
index 0000000..bde5c11
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolverTest.groovy
@@ -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.api.internal.artifacts.repositories.resolver
+
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class MavenResolverTest extends Specification {
+    def repositoryTransport = Mock(RepositoryTransport)
+    def repository = Mock(ExternalResourceRepository)
+
+    def rootUri = URI.create("localhost:8081:/testrepo/")
+    def locallyAvailableResourceFinder = Mock(LocallyAvailableResourceFinder)
+
+    def setup() {
+        repositoryTransport.getRepository() >> repository
+    }
+
+    @Unroll
+    def "setUseMavenMetaData '#value' adapts versionLister to #classname"() {
+        setup:
+        MavenResolver testresolver = new MavenResolver("test maven resolver", rootUri, repositoryTransport, locallyAvailableResourceFinder)
+        when:
+        testresolver.setUseMavenMetadata(value)
+        then:
+        testresolver.versionLister.class.name == classname
+        where:
+        value << [true, false]
+        classname << [ChainedVersionLister.class.name, ResourceVersionLister.class.name]
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionListerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionListerTest.groovy
new file mode 100644
index 0000000..1538348
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenVersionListerTest.groovy
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolver
+
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.externalresource.ExternalResource
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
+import org.gradle.api.internal.resource.ResourceException
+import org.gradle.api.internal.resource.ResourceNotFoundException
+import org.xml.sax.SAXParseException
+import spock.lang.Specification
+
+class MavenVersionListerTest extends Specification {
+    def repo = Mock(ExternalResourceRepository)
+    def moduleRevisionId = ModuleRevisionId.newInstance("org.acme", "testproject", "1.0")
+    def artifact = new DefaultArtifact(moduleRevisionId, new Date(), "testproject", "jar", "jar")
+
+    def repository = Mock(ExternalResourceRepository)
+    def pattern = pattern("localhost:8081/testRepo/" + MavenPattern.M2_PATTERN)
+    String metaDataResource = 'localhost:8081/testRepo/org/acme/testproject/maven-metadata.xml'
+
+    final org.gradle.api.internal.artifacts.repositories.resolver.MavenVersionLister lister = new org.gradle.api.internal.artifacts.repositories.resolver.MavenVersionLister(repository)
+
+    def "visit parses maven-metadata.xml"() {
+        ExternalResource resource = Mock()
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern, artifact)
+
+        then:
+        versionList.versionStrings == ['1.1', '1.2'] as Set
+
+        and:
+        1 * repository.getResource(metaDataResource) >> resource
+        1 * resource.openStream() >> new ByteArrayInputStream("""
+<metadata>
+    <versioning>
+        <versions>
+            <version>1.1</version>
+            <version>1.2</version>
+        </versions>
+    </versioning>
+</metadata>""".bytes)
+        1 * resource.close()
+        0 * repository._
+        0 * resource._
+    }
+
+    def "visit builds union of versions"() {
+        ExternalResource resource1 = Mock()
+        ExternalResource resource2 = Mock()
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern("prefix1/" + MavenPattern.M2_PATTERN), artifact)
+        versionList.visit(pattern("prefix2/" + MavenPattern.M2_PATTERN), artifact)
+
+        then:
+        versionList.versionStrings == ['1.1', '1.2', '1.3'] as Set
+
+        and:
+        1 * repository.getResource('prefix1/org/acme/testproject/maven-metadata.xml') >> resource1
+        1 * resource1.openStream() >> new ByteArrayInputStream("""
+<metadata>
+    <versioning>
+        <versions>
+            <version>1.1</version>
+            <version>1.2</version>
+        </versions>
+    </versioning>
+</metadata>""".bytes)
+        1 * repository.getResource('prefix2/org/acme/testproject/maven-metadata.xml') >> resource2
+        1 * resource2.openStream() >> new ByteArrayInputStream("""
+<metadata>
+    <versioning>
+        <versions>
+            <version>1.2</version>
+            <version>1.3</version>
+        </versions>
+    </versioning>
+</metadata>""".bytes)
+    }
+
+    def "visit ignores duplicate patterns"() {
+        ExternalResource resource = Mock()
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern, artifact)
+        versionList.visit(pattern, artifact)
+
+        then:
+        versionList.versionStrings == ['1.1', '1.2'] as Set
+
+        and:
+        1 * repository.getResource(metaDataResource) >> resource
+        1 * resource.openStream() >> new ByteArrayInputStream("""
+<metadata>
+    <versioning>
+        <versions>
+            <version>1.1</version>
+            <version>1.2</version>
+        </versions>
+    </versioning>
+</metadata>""".bytes)
+        1 * resource.close()
+        0 * repository._
+        0 * resource._
+    }
+
+    def "visit throws ResourceNotFoundException when maven-metadata not available"() {
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern, artifact)
+
+        then:
+        ResourceNotFoundException e = thrown()
+        e.message == "Maven meta-data not available: $metaDataResource"
+        1 * repository.getResource(metaDataResource) >> null
+        0 * repository._
+    }
+
+    def "visit throws ResourceException when maven-metadata cannot be parsed"() {
+        ExternalResource resource = Mock()
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern, artifact)
+
+        then:
+        ResourceException e = thrown()
+        e.message == "Unable to load Maven meta-data from $metaDataResource."
+        e.cause instanceof SAXParseException
+        1 * resource.close()
+        1 * repository.getResource(metaDataResource) >> resource;
+        1 * resource.openStream() >> new ByteArrayInputStream("yo".bytes)
+        0 * repository._
+    }
+
+    def "visit throws ResourceException when maven-metadata cannot be loaded"() {
+        def failure = new IOException()
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern, artifact)
+
+        then:
+        ResourceException e = thrown()
+        e.message == "Unable to load Maven meta-data from $metaDataResource."
+        e.cause == failure
+        1 * repository.getResource(metaDataResource) >> { throw failure }
+        0 * repository._
+    }
+
+    def pattern(String pattern) {
+        return new org.gradle.api.internal.artifacts.repositories.resolver.M2ResourcePattern(pattern)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionListerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionListerTest.groovy
new file mode 100644
index 0000000..42128df
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ResourceVersionListerTest.groovy
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.resolver
+
+import org.apache.ivy.core.module.descriptor.DefaultArtifact
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.externalresource.transport.ExternalResourceRepository
+import org.gradle.api.internal.resource.ResourceException
+import org.gradle.api.internal.resource.ResourceNotFoundException
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ResourceVersionListerTest extends Specification {
+
+    def repo = Mock(ExternalResourceRepository)
+    def moduleRevisionId = ModuleRevisionId.newInstance("org.acme", "proj1", "1.0")
+    def artifact = new DefaultArtifact(moduleRevisionId, new Date(), "proj1", "jar", "jar")
+
+    def org.gradle.api.internal.artifacts.repositories.resolver.ResourceVersionLister lister;
+
+    def setup() {
+        lister = new org.gradle.api.internal.artifacts.repositories.resolver.ResourceVersionLister(repo)
+    }
+
+    def "visit propagates Exceptions as ResourceException"() {
+        setup:
+        def failure = new IOException("Test IO Exception")
+        def testPattern = pattern("/a/pattern/with/[revision]/")
+        1 * repo.list(_) >> { throw failure }
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(testPattern, artifact)
+
+        then:
+        ResourceException e = thrown()
+        e.message == "Could not list versions using Ivy pattern '/a/pattern/with/[revision]/'."
+        e.cause == failure
+    }
+
+    def "visit throws ResourceNotFoundException for missing resource"() {
+        setup:
+        1 * repo.list(_) >> null
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern(testPattern), artifact)
+
+        then:
+        ResourceNotFoundException e = thrown()
+        e.message == "Cannot list versions from /some/."
+
+        where:
+        testPattern << ["/some/[revision]", "/some/version-[revision]"]
+    }
+
+    def "visit returns empty VersionList when repository contains empty list"() {
+        setup:
+        1 * repo.list(_) >> []
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern("/some/[revision]"), artifact)
+
+        then:
+        versionList.empty
+    }
+
+    @Unroll
+    def "visit resolves versions from from pattern with '#testPattern'"() {
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern(testPattern), artifact)
+
+        then:
+        versionList.versionStrings == ["1", "2.1", "a-version"] as Set
+
+        and:
+        1 * repo.list(repoListingPath) >> repoResult
+        0 * repo._
+
+        where:
+        testPattern                              | repoListingPath | repoResult
+        "[revision]"                             | ""              | ["1", "2.1/", "a-version"]
+        "[revision]/"                            | ""              | ["1", "2.1/", "a-version"]
+        "/[revision]"                            | "/"             | ["1", "2.1/", "a-version"]
+        "/[revision]/"                           | "/"             | ["1", "2.1/", "a-version"]
+        "/some/[revision]"                       | "/some/"        | ["/some/1", "/some/2.1/", "/some/a-version"]
+        "/some/[revision]/"                      | "/some/"        | ["/some/1", "/some/2.1/", "/some/a-version"]
+        "/some/[revision]/lib"                   | "/some/"        | ["/some/1/", "/some/2.1", "/some/a-version"]
+        "/some/version-[revision]"               | "/some/"        | ["/some/version-1", "/some/version-2.1", "/some/version-a-version", "/some/nonmatching"]
+        "/some/version-[revision]/lib"           | "/some/"        | ["/some/version-1", "/some/version-2.1", "/some/version-a-version", "/some/nonmatching"]
+        "/some/version-[revision]/lib/"          | "/some/"        | ["/some/version-1", "/some/version-2.1", "/some/version-a-version", "/some/nonmatching"]
+        "/some/[revision]-version"               | "/some/"        | ["/some/1-version", "/some/2.1-version", "/some/a-version-version", "/some/nonmatching"]
+        "/some/[revision]-version/lib"           | "/some/"        | ["/some/1-version", "/some/2.1-version", "/some/a-version-version", "/some/nonmatching"]
+        "/some/[revision]-lib.[ext]"             | "/some/"        | ["/some/1-lib.jar", "/some/1-lib.zip", "/some/2.1-lib.jar", "/some/a-version-lib.jar", "/some/nonmatching"]
+        "/some/any-[revision]-version/lib"       | "/some/"        | ["/some/any-1-version", "/some/any-2.1-version", "/some/any-a-version-version", "/some/nonmatching"]
+        "/some/any-[revision]-version/lib/"      | "/some/"        | ["/some/any-1-version", "/some/any-2.1-version", "/some/any-a-version-version", "/some/nonmatching"]
+        "/some/[revision]/lib/myjar-[revision]/" | "/some/"        | ["/some/1", "/some/2.1", "/some/a-version"]
+        "/some/proj-[revision]/[revision]/lib/"  | "/some/"        | ["/some/proj-1", "/some/proj-2.1", "/some/proj-a-version"]
+    }
+
+    def "visit builds union of versions"() {
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern("/[revision]/[artifact]-[revision].[ext]"), artifact)
+        versionList.visit(pattern("/[organisation]/[revision]/[artifact]-[revision].[ext]"), artifact)
+
+        then:
+        versionList.versionStrings == ["1.2", "1.3", "1.4"] as Set
+
+        and:
+        1 * repo.list("/") >> ["1.2", "1.3"]
+        1 * repo.list("/org.acme/") >> ["1.3", "1.4"]
+        0 * repo._
+    }
+
+    def "visit ignores duplicate patterns"() {
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern("/a/[revision]/[artifact]-[revision].[ext]"), artifact)
+        versionList.visit(pattern("/a/[revision]/[artifact]-[revision]"), artifact)
+
+        then:
+        versionList.versionStrings == ["1.2", "1.3"] as Set
+
+        and:
+        1 * repo.list("/a/") >> ["1.2", "1.3"]
+        0 * repo._
+    }
+
+    def "visit substitutes non revision placeholders from pattern before hitting repository"() {
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern(inputPattern), artifact)
+
+        then:
+        1 * repo.list(repoPath) >> ['1.2']
+
+        where:
+        inputPattern                                  | repoPath
+        "/[organisation]/[revision]"                  | "/org.acme/"
+        "/[organization]/[revision]"                  | "/org.acme/"
+        "/[module]/[revision]"                        | "/proj1/"
+        "/[module]/[revision]-lib.[ext]"              | "/proj1/"
+        "/[organisation]/[module]/[revision]"         | "/org.acme/proj1/"
+        "/[revision]/[module]/[organisation]"         | "/"
+        "/[type]s/[module]/[organisation]/[revision]" | "/jars/proj1/org.acme/"
+    }
+
+    def "visit returns empty version list when pattern has no revision token"() {
+        setup:
+        repo.list(_) >> repoResult
+
+        when:
+        def versionList = lister.getVersionList(moduleRevisionId)
+        versionList.visit(pattern(testPattern), artifact)
+
+        then:
+        versionList.empty
+
+        where:
+        testPattern                      | repoResult
+        "/some/pattern/with/no/revision" | ["/some/1-version", "/some/2.1-version", "/some/a-version-version"]
+    }
+
+    def pattern(String pattern) {
+        return new org.gradle.api.internal.artifacts.repositories.resolver.IvyResourcePattern(pattern)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListenerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListenerTest.groovy
new file mode 100644
index 0000000..dd2c72a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/transport/ProgressLoggingTransferListenerTest.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.api.internal.artifacts.repositories.transport
+
+import spock.lang.Specification
+import org.apache.ivy.plugins.repository.TransferEvent
+import org.apache.ivy.plugins.repository.Resource
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.logging.ProgressLogger
+
+class ProgressLoggingTransferListenerTest extends Specification {
+    TransferEvent transferEvent = Mock()
+    Resource resource = Mock()
+    ProgressLoggerFactory progressLoggerFactory = Mock()
+    org.gradle.api.internal.artifacts.repositories.transport.ProgressLoggingTransferListener progressLoggingTransferListener = new org.gradle.api.internal.artifacts.repositories.transport.ProgressLoggingTransferListener(progressLoggerFactory, null)
+    ProgressLogger progressLogger = Mock()
+
+    def setup() {
+        transferEvent.getResource() >> resource
+    }
+
+    def "transferProgress does not log operations on local resources"() {
+        setup:
+        resource.isLocal() >> true
+        when:
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        then:
+        0 * progressLoggerFactory.newOperation(_)
+        0 * progressLogger.started()
+        0 * progressLogger.progress(_)
+        0 * progressLogger.completed()
+    }
+
+    def "transferProgress logs started transfers"() {
+        setup:
+        transferEvent.getEventType() >> TransferEvent.TRANSFER_STARTED
+        when:
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        then:
+        1 * progressLoggerFactory.newOperation(_) >> progressLogger
+
+        0 * progressLogger.progress(_)
+        0 * progressLogger.completed()
+    }
+
+    def "transferProgress logs progress on transfers"() {
+        setup:
+        progressLoggerFactory.newOperation(_) >> progressLogger
+        transferEvent.getLength() >>> [512, 512, 2048, 256]
+        transferEvent.getEventType() >>> [TransferEvent.TRANSFER_STARTED, TransferEvent.TRANSFER_PROGRESS, TransferEvent.TRANSFER_PROGRESS, TransferEvent.TRANSFER_PROGRESS, TransferEvent.TRANSFER_PROGRESS]
+        when:
+        //create progressLogger
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        and:
+        //log progress
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        progressLoggingTransferListener.transferProgress(transferEvent)
+        then:
+        2 * progressLogger.progress(_)
+        0 * progressLogger.completed()
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy
new file mode 100644
index 0000000..919106c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy
@@ -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.internal.artifacts.result
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.*
+
+/**
+ * by Szczepan Faber, created at: 9/20/12
+ */
+class DefaultResolutionResultTest extends Specification {
+
+    def "provides all modules and dependencies including unresolved"() {
+        given:
+        def dep1 = newDependency('dep1')
+        def dep2 = newDependency('dep2')
+
+        def root = newModule('root').addDependency(dep1).addDependency(dep2)
+
+        def dep3 = newDependency('dep3')
+        def dep4 = newUnresolvedDependency('dep4')
+
+        dep2.selected.addDependency(dep3).addDependency(dep4)
+
+        when:
+        def deps = new DefaultResolutionResult(root).allDependencies
+        def modules = new DefaultResolutionResult(root).allModuleVersions
+
+        then:
+        deps == [dep1, dep2, dep3, dep4] as Set
+
+        and:
+        //does not contain unresolved dep, contains root
+        modules == [root, dep1.selected, dep2.selected, dep3.selected] as Set
+    }
+
+    def "provides hooks for iterating each module or dependency exactly once"() {
+        given:
+        //root -> dep1,dep2; dep1 -> dep3
+        def dep = newDependency('dep1')
+        def dep3 = newDependency('dep3')
+        def root = newModule('root').addDependency(dep).addDependency(newDependency('dep2')).addDependency(dep3)
+        dep.selected.addDependency(dep3)
+
+        def result = new DefaultResolutionResult(root)
+
+        when:
+        def deps = []
+        def modules = []
+        result.allDependencies { deps << it }
+        result.allModuleVersions { modules << it }
+
+        then:
+        deps*.requested.group == ['dep1', 'dep3', 'dep2', 'dep3']
+
+        and:
+        modules*.id.group == ['root', 'dep1', 'dep3', 'dep2']
+    }
+
+    def "deals with dependency cycles"() {
+        given:
+        // a->b->a
+        def root = newModule('a', 'a', '1')
+        def dep1 = newDependency('b', 'b', '1')
+        root.addDependency(dep1)
+        dep1.selected.addDependency(new DefaultResolvedDependencyResult(newSelector('a', 'a', '1'), root, dep1.selected))
+
+        when:
+        def deps = new DefaultResolutionResult(root).allDependencies
+        def modules = new DefaultResolutionResult(root).allModuleVersions
+
+        then:
+        deps.size() == 2
+        modules.size() == 2
+    }
+
+    def "mutating all dependencies or modules is harmless"() {
+        given:
+        def dep1 = newDependency('dep1')
+        def dep2 = newDependency('dep2')
+
+        def root = newModule('root').addDependency(dep1).addDependency(dep2)
+
+        when:
+        def result = new DefaultResolutionResult(root)
+
+        then:
+        result.allDependencies == [dep1, dep2] as Set
+        result.allModuleVersions == [root, dep1.selected, dep2.selected] as Set
+
+        when:
+        result.allDependencies << newDependency('dep3')
+        result.allModuleVersions << newModule('foo')
+
+        then:
+        result.allDependencies == [dep1, dep2] as Set
+        result.allModuleVersions == [root, dep1.selected, dep2.selected] as Set
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResultSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResultSpec.groovy
new file mode 100644
index 0000000..127622c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolvedModuleVersionResultSpec.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.result
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.*
+
+/**
+ * Created: 10/08/2012
+ * @author Szczepan Faber
+ */
+class DefaultResolvedModuleVersionResultSpec extends Specification {
+
+    def "mutating dependencies or dependents is harmless"() {
+        given:
+        def module = newModule("a", "c", "1")
+        def dependency  = newDependency("a", "x", "1")
+        def dependent   = newDependency("a", "x2", "1")
+
+        when:
+        module.addDependency(dependency)
+        module.addDependent(dependent)
+
+        then:
+        module.dependencies == [dependency] as Set
+        module.dependents   == [dependent] as Set
+
+        when:
+        module.dependencies << newDependency("a", "y", "1")
+        then:
+        thrown(UnsupportedOperationException)
+
+        when:
+        module.dependents <<   newDependency("a", "y2", "1")
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "includes unresolved dependencies"() {
+        given:
+        def module = newModule()
+        def dependency = newDependency()
+        def unresolved = newUnresolvedDependency()
+
+        when:
+        module.addDependency(dependency)
+        module.addDependency(unresolved)
+
+        then:
+        module.dependencies == [dependency, unresolved] as Set
+    }
+}
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
index 178a326..b63ed1b 100644
--- 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
@@ -15,7 +15,6 @@
  */
 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
@@ -31,7 +30,6 @@ public class CachedExternalResourceAdapterTest extends Specification {
 
     ExternalResourceAccessor accessor = Mock()
     CachedExternalResource cachedExternalResource = Mock()
-    CopyProgressListener progress = Mock()
     CachedExternalResourceAdapter cachedResource
     def origin = tmpDir.file('origin')
     def destination = tmpDir.file('destination')
@@ -59,7 +57,7 @@ public class CachedExternalResourceAdapterTest extends Specification {
         origin << "some content"
 
         when:
-        cachedResource.writeTo(destination, progress)
+        cachedResource.writeTo(destination)
 
         then:
         destination.assertIsCopyOf(origin)
@@ -72,7 +70,7 @@ public class CachedExternalResourceAdapterTest extends Specification {
         ExternalResource resource = Mock()
 
         when:
-        cachedResource.writeTo(destination, progress)
+        cachedResource.writeTo(destination)
 
         then:
         cachedExternalResource.cachedFile >> origin
@@ -80,6 +78,6 @@ public class CachedExternalResourceAdapterTest extends Specification {
 
         and:
         accessor.getResource("resource-source") >> resource
-        resource.writeTo(destination, progress)
+        resource.writeTo(destination)
     }
 }
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
index 468d827..13312f4 100644
--- 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
@@ -16,40 +16,38 @@
 
 package org.gradle.api.internal.externalresource.transfer
 
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
 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 {
+    final accessor = Mock(ExternalResourceAccessor)
+    final index = Mock(CachedExternalResourceIndex)
+    final cache = new DefaultCacheAwareExternalResourceAccessor(accessor, index)
 
     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:
+        index.lookup("location") >> cached
         cached.getExternalResourceMetaData() >> cachedMetaData
-        accessor.getMetaData(location) >> remoteMetaData
+        accessor.getMetaData("location") >> remoteMetaData
         localCandidates.isNone() >> false
         remoteMetaData.sha1 >> sha1
         
         when:
-        def foundResource = cache.getResource(location, localCandidates, cached)
+        def foundResource = cache.getResource("location", localCandidates)
 
         then:
         0 * accessor.getResourceSha1(_)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessorTest.groovy
new file mode 100644
index 0000000..259dcc3
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceAccessorTest.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.externalresource.transfer
+
+import org.gradle.api.internal.externalresource.ExternalResource
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ProgressLoggingExternalResourceAccessorTest extends Specification {
+
+    ExternalResourceAccessor accessor = Mock()
+    ProgressLoggerFactory progressLoggerFactory = Mock();
+    ProgressLoggingExternalResourceAccessor progressLoggerAccessor = new ProgressLoggingExternalResourceAccessor(accessor, progressLoggerFactory)
+    ProgressLogger progressLogger = Mock()
+    ExternalResource externalResource = Mock()
+
+    @Unroll
+    def "delegates #method to delegate resource accessor"() {
+        when:
+        progressLoggerAccessor."$method"("location")
+        then:
+        1 * accessor."$method"("location")
+        where:
+        method << ['getMetaData', 'getResource', 'getResourceSha1']
+    }
+
+    def "getResource returns null when delegate returns null"() {
+        setup:
+        accessor.getResource("location") >> null
+        when:
+        def loadedResource = progressLoggerAccessor.getResource("location")
+        then:
+        loadedResource == null
+    }
+
+    def "getResource wraps loaded Resource from delegate in ProgressLoggingExternalResource"() {
+        setup:
+        accessor.getResource("location") >> externalResource
+        when:
+        def loadedResource = progressLoggerAccessor.getResource("location")
+        then:
+        loadedResource != null
+        loadedResource instanceof ProgressLoggingExternalResourceAccessor.ProgressLoggingExternalResource
+    }
+
+    def "ProgressLoggingExternalResource.writeTo wraps delegate call in progress logger"() {
+        setup:
+        accessor.getResource("location") >> externalResource
+        externalResource.getName() >> "test resource"
+        externalResource.getContentLength() >> 2060
+        externalResource.writeTo(_) >> { OutputStream stream ->
+            stream.write(12)
+            stream.write(2)
+            stream.write(112)
+            stream.write(new byte[1024])
+        }
+        when:
+        progressLoggerAccessor.getResource("location").writeTo(new ByteArrayOutputStream())
+        then:
+        1 * progressLoggerFactory.newOperation(_) >> progressLogger
+        1 * progressLogger.started()
+        1 * progressLogger.progress(_)
+        1 * progressLogger.completed()
+    }
+
+    def "no progress events logged for resources smaller 1024 bytes"() {
+        setup:
+        accessor.getResource("location") >> externalResource
+        externalResource.getName() >> "test resource"
+        externalResource.getContentLength() >> 1023
+        externalResource.writeTo(_) >> { OutputStream stream ->
+            stream.write(new byte[1023])
+        }
+        when:
+        progressLoggerAccessor.getResource("location").writeTo(new ByteArrayOutputStream())
+        then:
+        1 * progressLoggerFactory.newOperation(_) >> progressLogger
+        1 * progressLogger.started()
+        1 * progressLogger.completed()
+        0 * progressLogger.progress(_)
+    }
+
+    @Unroll
+    def "ProgressLoggingExternalResource delegates #method to delegate ExternalResource"() {
+        when:
+        accessor.getResource("location") >> externalResource
+        def plExternalResource = progressLoggerAccessor.getResource("location")
+        and:
+        plExternalResource."$method"()
+        then:
+        1 * externalResource."$method"()
+        where:
+        method << ['close', 'getMetaData', 'getName', 'getLastModified', 'getContentLength', 'isLocal', 'openStream']
+    }
+
+    @Unroll
+    def "ProgressLoggingExternalResource #method to delegate ExternalResource"() {
+        when:
+        accessor.getResource("location") >> externalResource
+        def plExternalResource = progressLoggerAccessor.getResource("location")
+        and:
+        plExternalResource."$method"()
+        then:
+        1 * externalResource."$method"()
+        where:
+        method << ['close', 'getMetaData', 'getName', 'getLastModified', 'getContentLength', 'isLocal', 'openStream', 'toString']
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploaderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploaderTest.groovy
new file mode 100644
index 0000000..25d09fb
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ProgressLoggingExternalResourceUploaderTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.internal.Factory
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
+
+class ProgressLoggingExternalResourceUploaderTest extends Specification {
+    ExternalResourceUploader uploader = Mock()
+    ProgressLoggerFactory progressLoggerFactory = Mock();
+    def progressLoggerUploader = new ProgressLoggingExternalResourceUploader(uploader, progressLoggerFactory)
+    ProgressLogger progressLogger = Mock()
+    InputStream inputStream = Mock();
+    Factory<InputStream> delegateFactory = Mock();
+
+    def "delegates upload to delegate uploader and logs progress"() {
+        setup:
+        startsProgress()
+
+        when:
+        progressLoggerUploader.upload(inputStreamFactory(), 5 * 1024, "http://a/remote/path")
+        then:
+        1 * delegateFactory.create() >> inputStream
+        1 * uploader.upload(_, 5 * 1024, "http://a/remote/path") >> {factory, length, destination ->
+            def stream = factory.create();
+            assert stream.read(new byte[1024]) == 1024
+            assert stream.read() == 48
+        }
+        1 * inputStream.read(_, 0, 1024) >> 1024
+        1 * inputStream.read() >> 48
+        1 * progressLogger.progress(_)
+        1 * progressLogger.completed()
+    }
+
+    private Factory inputStreamFactory() {
+        new Factory<InputStream>() {
+            InputStream create() {
+                return delegateFactory.create()
+            }
+        }
+    }
+
+    def startsProgress() {
+        1 * progressLoggerFactory.newOperation(ProgressLoggingExternalResourceUploader.class) >> progressLogger;
+        1 * progressLogger.setDescription("Upload http://a/remote/path")
+        1 * progressLogger.setLoggingHeader("Upload http://a/remote/path")
+        1 * progressLogger.started()
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperationTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperationTest.groovy
new file mode 100644
index 0000000..00e33c6
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/ResourceOperationTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.logging.ProgressLogger
+
+class ResourceOperationTest extends Specification {
+
+    ProgressLogger progressLogger = Mock()
+
+    def "no progress event is logged for files < 1024bytes"(){
+        given:
+        def operation = new ResourceOperation(progressLogger, ResourceOperation.Type.download, 1023)
+        when:
+        operation.logProcessedBytes(1023)
+        then:
+        0 * progressLogger.progress(_)
+    }
+
+    def "logs processed bytes in kbyte intervalls"() {
+        given:
+        def operation = new ResourceOperation(progressLogger, ResourceOperation.Type.download, 1024 * 10)
+        when:
+        operation.logProcessedBytes(512 * 0)
+        operation.logProcessedBytes(512 * 1)
+        then:
+        0 * progressLogger.progress(_)
+
+        when:
+        operation.logProcessedBytes(512 * 1)
+        operation.logProcessedBytes(512 * 2)
+        then:
+        1 * progressLogger.progress("1 KB/10 KB downloaded")
+        1 * progressLogger.progress("2 KB/10 KB downloaded")
+        0 * progressLogger.progress(_)
+    }
+
+    def "last chunk of bytes <1k is not logged"(){
+        given:
+        def operation = new ResourceOperation(progressLogger, ResourceOperation.Type.download, 2000)
+        when:
+        operation.logProcessedBytes(1000)
+        operation.logProcessedBytes(1000)
+        then:
+        1 * progressLogger.progress("1 KB/1 KB downloaded")
+        0 * progressLogger.progress(_)
+    }
+
+    def "adds operationtype information in progress output"() {
+        given:
+        def operation = new ResourceOperation(progressLogger, type, 1024 * 10)
+        when:
+        operation.logProcessedBytes(1024)
+        then:
+        1 * progressLogger.progress(message)
+        where:
+        type                            | message
+        ResourceOperation.Type.download | "1 KB/10 KB downloaded"
+        ResourceOperation.Type.upload   | "1 KB/10 KB uploaded"
+    }
+
+    void "completed completes progressLogger"() {
+        given:
+        def operation = new ResourceOperation(progressLogger, ResourceOperation.Type.upload, 1)
+        when:
+        operation.completed()
+        then:
+        1 * progressLogger.completed()
+    }
+
+    void "handles unknown content length"() {
+        given:
+        def operation = new ResourceOperation(progressLogger, ResourceOperation.Type.upload, 0)
+        when:
+        operation.logProcessedBytes(1024)
+        then:
+        1 * progressLogger.progress("1 KB/unknown size uploaded")
+    }
+}
+
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy
index 2b8b942..d8eb05f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy
@@ -27,7 +27,7 @@ import static org.junit.Assert.assertNotNull
 class ApacheDirectoryListingParserTest extends Specification {
     @Rule public final Resources resources = new Resources();
 
-    private static final CONTENT_TYPE= "text/html;charset=utf-8";
+    private static final CONTENT_TYPE = "text/html;charset=utf-8";
     private URI baseUrl = URI.create("http://testrepo/")
     private ApacheDirectoryListingParser parser = new ApacheDirectoryListingParser();
 
@@ -39,21 +39,21 @@ class ApacheDirectoryListingParserTest extends Specification {
     }
 
     def "addTrailingSlashes adds trailing slashes on relative URL if not exist"() {
-            expect:
-            new URI(resultingURI) == parser.addTrailingSlashes(new URI(inputURI))
-            where:
-            inputURI                        | resultingURI
-            "http://testrepo"               | "http://testrepo/"
-            "http://testrepo/"              | "http://testrepo/"
-            "http://testrepo/index.html"    | "http://testrepo/index.html"
-        }
+        expect:
+        new URI(resultingURI) == parser.addTrailingSlashes(new URI(inputURI))
+        where:
+        inputURI                     | resultingURI
+        "http://testrepo"            | "http://testrepo/"
+        "http://testrepo/"           | "http://testrepo/"
+        "http://testrepo/index.html" | "http://testrepo/index.html"
+    }
 
     def "parse handles multiple listed links"() {
         def html = """
         <a href="directory1">directory1</a>
         <a href="directory2">directory2</a>
         <a href="directory3">directory3</a>
-        <a href="directory4">directory4</a>"""
+        <a href="directory4"/>"""
         expect:
         def uris = parser.parse(baseUrl, html.bytes, CONTENT_TYPE)
         assertNotNull(uris)
@@ -72,6 +72,29 @@ class ApacheDirectoryListingParserTest extends Specification {
         contentType << ["text/plain", "application/octetstream"]
     }
 
+    def "uses charset specified in content type"() {
+        def html = """
+        <a href="\u00c1\u00d2">directory1</a>
+        """
+        def encodedHtml = html.getBytes('ISO-8859-1')
+        assert !Arrays.equals(encodedHtml, html.getBytes("utf-8"))
+
+        expect:
+        def uris = parser.parse(baseUrl, encodedHtml, 'text/html;charset=ISO-8859-1')
+        uris.collect {it.toString()} == ["http://testrepo/\u00c1\u00d2"]
+    }
+
+    def "defaults to utf-8 when no charset specified"() {
+        def html = """
+        <a href="\u0321\u0322">directory1</a>
+        """
+        def encodedHtml = html.getBytes('utf-8')
+
+        expect:
+        def uris = parser.parse(baseUrl, encodedHtml, 'text/html')
+        uris.collect {it.toString()} == ["http://testrepo/\u0321\u0322"]
+    }
+
     @Unroll
     def "parse ignores #descr"() {
         expect:
@@ -96,23 +119,23 @@ class ApacheDirectoryListingParserTest extends Specification {
         !foundURIs.isEmpty()
         foundURIs.collect {it.toString()} == ["${baseUri}/directory1"]
         where:
-        baseUri                 | href                        | urlDescr
-        "http://testrepo"       |  "directory1"               | "relative URLS"
-        "http://testrepo"       |"/directory1"                | "absolute URLS"
-        "http://testrepo"       |"./directory1"               | "explicit relative URLS"
-        "http://testrepo"       |"http://testrepo/directory1" | "complete URLS"
-        "http://testrepo"       | "http://testrepo/directory1"| "hrefs with truncated text"
-        "http://testrepo"       | "http://testrepo/directory1"| "hrefs with truncated text"
-        "http://[2001:db8::7]"  |"directory1"                 | "ipv6 host with relative URLS"
-        "http://[2001:db8::7]"  |"./directory1"               | "ipv6 host with explicit relative URLS"
-        "http://192.0.0.10"     |"directory1"                 | "ipv4 host with relative URLS"
-        "http://192.0.0.10"     |"./directory1"               | "ipv4 host with relative URLS"
+        baseUri                | href                         | urlDescr
+        "http://testrepo"      | "directory1"                 | "relative URLS"
+        "http://testrepo"      | "/directory1"                | "absolute URLS"
+        "http://testrepo"      | "./directory1"               | "explicit relative URLS"
+        "http://testrepo"      | "http://testrepo/directory1" | "complete URLS"
+        "http://testrepo"      | "http://testrepo/directory1" | "hrefs with truncated text"
+        "http://testrepo"      | "http://testrepo/directory1" | "hrefs with truncated text"
+        "http://[2001:db8::7]" | "directory1"                 | "ipv6 host with relative URLS"
+        "http://[2001:db8::7]" | "./directory1"               | "ipv6 host with explicit relative URLS"
+        "http://192.0.0.10"    | "directory1"                 | "ipv4 host with relative URLS"
+        "http://192.0.0.10"    | "./directory1"               | "ipv4 host with relative URLS"
     }
 
     @Unroll
     def "parse is compatible with #repoType"() {
         setup:
-        def byte[] content =  resources.getResource("${repoType}_dirlisting.html").bytes
+        def byte[] content = resources.getResource("${repoType}_dirlisting.html").bytes
         expect:
         List<URI> urls = new ApacheDirectoryListingParser().parse(new URI(artifactRootURI), content, CONTENT_TYPE)
         urls.collect {it.toString()} as Set == ["${artifactRootURI}3.7/",
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
index 097d063..e11c232 100644
--- 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
@@ -17,10 +17,10 @@ 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.apache.http.params.HttpProtocolParams
 import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.api.internal.resource.UriResource
 import spock.lang.Specification
-import org.apache.http.params.HttpProtocolParams
 
 public class HttpClientConfigurerTest extends Specification {
     DefaultHttpClient httpClient = new DefaultHttpClient()
@@ -28,19 +28,18 @@ public class HttpClientConfigurerTest extends Specification {
     HttpSettings httpSettings = Mock()
     HttpProxySettings proxySettings = Mock()
     HttpClientConfigurer configurer = new HttpClientConfigurer(httpSettings)
-    
+
     def "configures http client with no credentials or proxy"() {
         httpSettings.credentials >> credentials
         httpSettings.proxySettings >> proxySettings
 
         when:
         configurer.configure(httpClient)
-        
+
         then:
-        httpClient.getRoutePlanner() instanceof ProxySelectorRoutePlanner
-        httpClient.getHttpRequestRetryHandler().retryRequest(new IOException(), 1, null) == false
+        !httpClient.getHttpRequestRetryHandler().retryRequest(new IOException(), 1, null)
     }
-    
+
     def "configures http client with proxy credentials"() {
         httpSettings.credentials >> credentials
         httpSettings.proxySettings >> proxySettings
@@ -97,6 +96,6 @@ public class HttpClientConfigurerTest extends Specification {
         configurer.configure(httpClient)
 
         then:
-        HttpProtocolParams.getUserAgent(httpClient.params).startsWith('Gradle')
+        HttpProtocolParams.getUserAgent(httpClient.params) == UriResource.userAgentString
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.groovy
new file mode 100644
index 0000000..49f4f16
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelperTest.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.api.internal.externalresource.transport.http
+
+import org.apache.http.HttpResponse
+import org.apache.http.client.methods.HttpRequestBase
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.apache.http.client.methods.HttpGet
+
+import spock.lang.Specification
+
+class HttpClientHelperTest extends Specification {
+    def "throws HttpRequestException if an IO error occurs during a request"() {
+        def settings = Stub(HttpSettings) {
+            getCredentials() >> Stub(PasswordCredentials)
+            getProxySettings() >> Stub(HttpProxySettings)
+        }
+        def client = new HttpClientHelper(settings) {
+            @Override
+            protected HttpResponse executeGetOrHead(HttpRequestBase method) {
+                throw new IOException("ouch")
+            }
+        }
+
+        when:
+        client.performRequest(new HttpGet("http://gradle.org"))
+
+        then:
+        HttpRequestException e = thrown()
+        e.cause.message == "ouch"
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy
index 7eac387..147bdd2 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy
@@ -16,22 +16,22 @@
 
 package org.gradle.api.internal.externalresource.transport.http
 
-import org.gradle.api.internal.externalresource.ExternalResource
 import spock.lang.Specification
 
 class HttpResourceListerTest extends Specification {
 
-    HttpResourceAccessor accessorMock = Mock(HttpResourceAccessor)
-    ExternalResource externalResource = Mock(ExternalResource)
+    HttpResourceAccessor accessorMock = Mock()
+    HttpResponseResource externalResource = Mock()
     HttpResourceLister lister = new HttpResourceLister(accessorMock)
 
     def "consumeExternalResource closes resource after reading into stream"() {
         setup:
         accessorMock.getResource("http://testrepo/") >> externalResource;
         when:
-        lister.loadResourceContent(externalResource)
+        lister.list("http://testrepo/")
         then:
-        1 * externalResource.writeTo(_, _)
+        1 * externalResource.writeTo(_) >> {OutputStream outputStream -> outputStream.write("<a href='child'/>".bytes)}
+        1 * externalResource.getContentType() >> "text/html"
         1 * externalResource.close()
     }
 
diff --git a/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy b/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy
new file mode 100644
index 0000000..cded200
--- /dev/null
+++ b/subprojects/core-impl/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.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.api.internal.artifacts.result
+
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 10/2/12
+ */
+class ResolutionResultDataBuilder {
+
+    static DefaultResolvedDependencyResult newDependency(String group='a', String module='a', String version='1', String selectedVersion='1') {
+        new DefaultResolvedDependencyResult(newSelector(group, module, version), newModule(group, module, selectedVersion), newModule())
+    }
+
+    static DefaultUnresolvedDependencyResult newUnresolvedDependency(String group='x', String module='x', String version='1') {
+        new DefaultUnresolvedDependencyResult(newSelector(group, module, version), new RuntimeException("boo!"), newModule())
+    }
+
+    static DefaultResolvedModuleVersionResult newModule(String group='a', String module='a', String version='1',
+                                                        ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
+        new DefaultResolvedModuleVersionResult(newId(group, module, version), selectionReason)
+    }
+}
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
index 92af92e..71947b8 100755
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -25,6 +25,7 @@ dependencies {
     publishCompile project(":baseServices")
     publishCompile project(":messaging")
 
+    compile project(":baseServicesGroovy")
     compile libraries.asm
     compile libraries.ant
     compile libraries.commons_collections
@@ -37,6 +38,7 @@ dependencies {
     compile libraries.jcip
     compile libraries.jul_to_slf4j
     compile module('com.googlecode.jarjar:jarjar:1.3')
+    compile libraries.inject
 
     compile project(":cli")
     compile project(":native")
@@ -48,6 +50,7 @@ dependencies {
     testCompile libraries.jcl_to_slf4j
 
     testRuntime "xerces:xercesImpl:2.9.1"
+    testRuntime project(":diagnostics")
 
     testFixturesCompile project(":internalTesting")
     testFixturesRuntime project(':coreImpl')
@@ -59,10 +62,6 @@ dependencies {
 
 useTestFixtures()
 
-test {
-    jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
-}
-
 [compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
 
 task buildReceiptResource(type: Copy, dependsOn: rootProject.createBuildReceipt) {
@@ -90,7 +89,7 @@ class PluginsManifest extends DefaultTask {
     @Input
     Properties getPluginProperties() {
         def properties = new Properties()
-        properties.plugins = project.pluginProjects().collect { it.archivesBaseName }.join(',')
+        properties.plugins = project.pluginProjects.collect { it.archivesBaseName }.join(',')
         return properties
     }
 
@@ -98,4 +97,4 @@ class PluginsManifest extends DefaultTask {
     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/dsl/DynamicObjectIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
index 0c74d12..be51d9f 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
@@ -26,6 +26,10 @@ class DynamicObjectIntegrationTest {
     @Rule public final GradleDistribution dist = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
 
+    TestFile getBuildFile() {
+        dist.testDir.file("build.gradle")
+    }
+
     @Test
     public void canAddDynamicPropertiesToProject() {
         TestFile testDir = dist.getTestDir();
@@ -468,7 +472,7 @@ assert 'overridden value' == global
         executer.withDeprecationChecksDisabled()
         def result = executer.withTasks("run").run()
 
-        assert result.output.contains('Dynamic properties are deprecated: http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html')
+        assert result.output.contains('Creating properties on demand (a.k.a. dynamic properties) has been deprecated')
         assert result.output.contains('Deprecated dynamic property: "p1" on "root project ')
         assert result.output.contains('Deprecated dynamic property: "p2" on "task \':run\'", value: "2".')
     }
@@ -493,4 +497,41 @@ assert 'overridden value' == global
 
         executer.withTasks("run").run()
     }
+
+    @Issue("GRADLE-2417")
+    @Test void canHaveDynamicExtension() {
+        buildFile << """
+            class DynamicThing {
+                def methods = [:]
+                def props = [:]
+
+                def methodMissing(String name, args) {
+                    methods[name] = args.toList()
+                }
+
+                def propertyMissing(String name) {
+                    props[name]
+                }
+
+                def propertyMissing(String name, value) {
+                    props[name] = value
+                }
+            }
+
+            extensions.create("dynamic", DynamicThing)
+
+            dynamic {
+                m1(1,2,3)
+                p1 = 1
+                p1 += 1
+            }
+
+            task run << {
+                assert dynamic.methods.size() == 1
+                assert dynamic.props.p1 == 2
+            }
+        """
+
+        executer.withTasks("run").run()
+    }
 }
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
index 37ed2dd..27c2445 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
@@ -19,16 +19,13 @@ package org.gradle.groovy.scripts
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class StatementLabelsIntegrationTest extends AbstractIntegrationSpec {
-    def setup() {
-        executer.withDeprecationChecksDisabled()
-    }
-
     def "use of statement label in build script is flagged"() {
         buildFile << """
 version: '1.0'
         """
 
         expect:
+        executer.withDeprecationChecksDisabled()
         succeeds("tasks")
         output.contains("Usage of statement labels in build scripts has been deprecated")
         output.contains("version")
@@ -48,6 +45,7 @@ description: "bar"
         """
 
         expect:
+        executer.withDeprecationChecksDisabled()
         succeeds("tasks")
         output.contains("Usage of statement labels in build scripts has been deprecated")
         output.contains("version")
@@ -67,6 +65,7 @@ def foo() {
         """
 
         expect:
+        executer.withDeprecationChecksDisabled()
         succeeds("tasks")
         output.contains("Usage of statement labels in build scripts has been deprecated")
         output.contains("label")
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
index d16ac6a..265cd9e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -21,18 +21,21 @@ import org.gradle.api.GradleException;
 import org.gradle.api.internal.LocationAwareException;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.configuration.ImplicitTasksConfigurer;
+import org.gradle.execution.MultipleBuildFailures;
 import org.gradle.execution.TaskSelectionException;
 import org.gradle.initialization.BuildClientMetaData;
 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.BufferingStyledTextOutput;
 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.List;
+
 import static org.gradle.logging.StyledTextOutput.Style.*;
 
 /**
@@ -63,75 +66,61 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
     }
 
     public void execute(Throwable failure) {
-        FailureDetails details = new FailureDetails(failure);
-        if (failure instanceof GradleException) {
-            reportBuildFailure((GradleException) failure, details);
-        } else {
-            reportInternalError(details);
+        if (failure instanceof MultipleBuildFailures) {
+            renderMultipleBuildExceptions((MultipleBuildFailures) failure);
+            return;
         }
 
-        write(details);
+        renderSingleBuildException(failure);
     }
 
-    protected void write(FailureDetails details) {
-        StyledTextOutput output = textOutputFactory.create(BuildExceptionReporter.class, LogLevel.ERROR);
+    private void renderMultipleBuildExceptions(MultipleBuildFailures multipleFailures) {
+        List<? extends Throwable> causes = multipleFailures.getCauses();
 
+        StyledTextOutput output = textOutputFactory.create(BuildExceptionReporter.class, LogLevel.ERROR);
+        output.println();
+        output.withStyle(Failure).format("FAILURE: Build completed with %s failures.", causes.size());
         output.println();
-        output.withStyle(Failure).text("FAILURE: ");
-        details.summary.writeTo(output.withStyle(Failure));
 
-        if (details.location.getHasContent()) {
-            output.println().println();
-            output.println("* Where:");
-            details.location.writeTo(output);
-        }
+        for (int i = 0; i < causes.size(); i++) {
+            Throwable cause = causes.get(i);
+            FailureDetails details = constructFailureDetails("Task", cause);
 
-        if (details.details.getHasContent()) {
-            output.println().println();
-            output.println("* What went wrong:");
-            details.details.writeTo(output);
-        }
+            output.println();
+            output.withStyle(Failure).format("%s: ", i + 1);
+            details.summary.writeTo(output.withStyle(Failure));
+            output.println();
+            output.text("-----------");
 
-        if (details.resolution.getHasContent()) {
-            output.println().println();
-            output.println("* Try:");
-            details.resolution.writeTo(output);
-        }
+            writeFailureDetails(output, details);
 
-        Throwable exception = null;
-        switch (details.exceptionStyle) {
-            case NONE:
-                break;
-            case SANITIZED:
-                exception = StackTraceUtils.deepSanitize(details.failure);
-                break;
-            case FULL:
-                exception = details.failure;
-                break;
+            output.println("==============================================================================");
         }
+    }
 
-        if (exception != null) {
-            output.println().println();
-            output.println("* Exception is:");
-            output.exception(exception);
-        }
+    private void renderSingleBuildException(Throwable failure) {
+        StyledTextOutput output = textOutputFactory.create(BuildExceptionReporter.class, LogLevel.ERROR);
+        FailureDetails details = constructFailureDetails("Build", failure);
 
         output.println();
-    }
+        output.withStyle(Failure).text("FAILURE: ");
+        details.summary.writeTo(output.withStyle(Failure));
+        output.println();
 
-    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://forums.gradle.org.");
+        writeFailureDetails(output, details);
+    }
 
-        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.");
-            details.exceptionStyle = ExceptionStyle.FULL;
+    private FailureDetails constructFailureDetails(String granularity, Throwable failure) {
+        FailureDetails details = new FailureDetails(failure);
+        if (failure instanceof GradleException) {
+            reportBuildFailure(granularity, (GradleException) failure, details);
+        } else {
+            reportInternalError(details);
         }
+        return details;
     }
 
-    private void reportBuildFailure(GradleException failure, FailureDetails details) {
+    private void reportBuildFailure(String granularity, GradleException failure, FailureDetails details) {
         if (loggingConfiguration.getShowStacktrace() == ShowStacktrace.ALWAYS || loggingConfiguration.getLogLevel() == LogLevel.DEBUG) {
             details.exceptionStyle = ExceptionStyle.SANITIZED;
         }
@@ -142,7 +131,7 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
         if (failure instanceof TaskSelectionException) {
             formatTaskSelectionFailure((TaskSelectionException) failure, details);
         } else {
-            formatGenericFailure(failure, details);
+            formatGenericFailure(granularity, failure, details);
         }
     }
 
@@ -155,8 +144,8 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
         details.resolution.text(" to get a list of available tasks.");
     }
 
-    private void formatGenericFailure(GradleException failure, final FailureDetails details) {
-        details.summary.text("Build failed with an exception.");
+    private void formatGenericFailure(String granularity, GradleException failure, final FailureDetails details) {
+        details.summary.format("%s failed with an exception.", granularity);
 
         fillInFailureResolution(details);
 
@@ -182,7 +171,7 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
                         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));
                     }
@@ -229,6 +218,60 @@ public class BuildExceptionReporter extends BuildAdapter implements Action<Throw
         return String.format("%s (no error message)", throwable.getClass().getName());
     }
 
+    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://forums.gradle.org.");
+
+        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.");
+            details.exceptionStyle = ExceptionStyle.FULL;
+        }
+    }
+
+    private void writeFailureDetails(StyledTextOutput output, FailureDetails details) {
+        if (details.location.getHasContent()) {
+            output.println();
+            output.println("* Where:");
+            details.location.writeTo(output);
+            output.println();
+        }
+
+        if (details.details.getHasContent()) {
+            output.println();
+            output.println("* What went wrong:");
+            details.details.writeTo(output);
+            output.println();
+        }
+
+        if (details.resolution.getHasContent()) {
+            output.println();
+            output.println("* Try:");
+            details.resolution.writeTo(output);
+            output.println();
+        }
+
+        Throwable exception = null;
+        switch (details.exceptionStyle) {
+            case NONE:
+                break;
+            case SANITIZED:
+                exception = StackTraceUtils.deepSanitize(details.failure);
+                break;
+            case FULL:
+                exception = details.failure;
+                break;
+        }
+
+        if (exception != null) {
+            output.println();
+            output.println("* Exception is:");
+            output.exception(exception);
+            output.println();
+        }
+    }
+
     private static class FailureDetails {
         Throwable failure;
         final BufferingStyledTextOutput summary = new BufferingStyledTextOutput();
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index ddc45ad..8419e04 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -66,6 +66,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private File projectCacheDir;
     private boolean refreshDependencies;
     private boolean recompileScripts;
+    private int parallelThreadCount;
 
     /**
      * Sets the project's cache location. Set to null to use the default location.
@@ -130,6 +131,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         startParameter.continueOnFailure = continueOnFailure;
         startParameter.offline = offline;
         startParameter.refreshDependencies = refreshDependencies;
+        startParameter.parallelThreadCount = parallelThreadCount;
         return startParameter;
     }
 
@@ -152,6 +154,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         startParameter.rerunTasks = rerunTasks;
         startParameter.recompileScripts = recompileScripts;
         startParameter.refreshDependencies = refreshDependencies;
+        startParameter.parallelThreadCount = parallelThreadCount;
         return startParameter;
     }
 
@@ -585,6 +588,26 @@ public class StartParameter extends LoggingConfiguration implements Serializable
         this.recompileScripts = recompileScripts;
     }
 
+    /**
+     * Returns the number of parallel threads to use for build execution.
+     *
+     * <0: Automatically determine the optimal number of executors to use.
+     *  0: Do not use parallel execution.
+     * >0: Use this many parallel execution threads.
+     */
+    public int getParallelThreadCount() {
+        return parallelThreadCount;
+    }
+
+    /**
+     * Specifies the number of parallel threads to use for build execution.
+     * 
+     * @see #getParallelThreadCount()
+     */
+    public void setParallelThreadCount(int parallelThreadCount) {
+        this.parallelThreadCount = parallelThreadCount;
+    }
+
     @Override
     public String toString() {
         return "StartParameter{"
diff --git a/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java b/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java
index 3de80b5..af71a38 100644
--- a/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/TaskExecutionLogger.java
@@ -24,11 +24,15 @@ import org.gradle.api.tasks.TaskState;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * A listener which logs the execution of tasks.
  */
 public class TaskExecutionLogger implements TaskExecutionListener {
-    private ProgressLogger currentTask;
+    // TODO:PARALLEL Seems to be some thread-safety issues here (get 'failed' logged for wrong task)
+    private final Map<Task, ProgressLogger> currentTasks = new HashMap<Task, ProgressLogger>();
     private final ProgressLoggerFactory progressLoggerFactory;
 
     public TaskExecutionLogger(ProgressLoggerFactory progressLoggerFactory) {
@@ -36,18 +40,21 @@ public class TaskExecutionLogger implements TaskExecutionListener {
     }
 
     public void beforeExecute(Task task) {
-        assert currentTask == null;
-        currentTask = progressLoggerFactory.newOperation(TaskExecutionLogger.class);
+        assert !currentTasks.containsKey(task);
+
+        ProgressLogger currentTask = progressLoggerFactory.newOperation(TaskExecutionLogger.class);
         String displayName = getDisplayName(task);
         currentTask.setDescription(String.format("Execute %s", displayName));
         currentTask.setShortDescription(displayName);
         currentTask.setLoggingHeader(displayName);
         currentTask.started();
+        currentTasks.put(task, currentTask);
     }
 
     public void afterExecute(Task task, TaskState state) {
-        currentTask.completed(state.getSkipMessage());
-        currentTask = null;
+        ProgressLogger currentTask = currentTasks.remove(task);
+        String taskMessage = state.getFailure() != null ? "FAILED" : state.getSkipMessage();
+        currentTask.completed(taskMessage);
     }
 
     private String getDisplayName(Task task) {
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 46aa564..0a16302 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -27,6 +27,7 @@ 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.HasInternalProtocol;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.LoggingManager;
@@ -191,6 +192,7 @@ import java.util.Set;
  *
  * @author Hans Dockter
  */
+ at HasInternalProtocol
 public interface Project extends Comparable<Project>, ExtensionAware {
     /**
      * The default project build file name.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java b/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java
deleted file mode 100644
index 7527bf6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api;
-
-/**
- * <code>UncheckedIOException</code> is used to wrap an
- * java.io.IOException into an unchecked exception.
- * 
- * @author Hans Dockter
- */
-public class UncheckedIOException extends RuntimeException {
-    public UncheckedIOException() {
-    }
-
-    public UncheckedIOException(String message) {
-        super(message);
-    }
-
-    public UncheckedIOException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UncheckedIOException(Throwable cause) {
-        super(cause);
-    }
-}
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 b6f6700..5e774f2 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
@@ -17,6 +17,7 @@ package org.gradle.api.artifacts;
 
 import groovy.lang.Closure;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.HasInternalProtocol;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 
@@ -36,6 +37,7 @@ import java.util.Set;
  * Read more about declaring artifacts in the configuration in docs for {@link org.gradle.api.artifacts.dsl.ArtifactHandler}
  * <p>
  */
+ at HasInternalProtocol
 public interface Configuration extends FileCollection {
 
     /**
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 6af7586..db1890f 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
@@ -18,6 +18,7 @@ package org.gradle.api.artifacts;
 import groovy.lang.Closure;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.internal.HasInternalProtocol;
 
 /**
  * <p>A {@code ConfigurationContainer} is responsible for declaring and managing configurations. See also {@link Configuration}.</p>
@@ -81,6 +82,7 @@ import org.gradle.api.NamedDomainObjectContainer;
  *
  * @author Hans Dockter
  */
+ at HasInternalProtocol
 public interface ConfigurationContainer extends NamedDomainObjectContainer<Configuration> {
     /**
      * {@inheritDoc}
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
index fc23ef9..d7de164 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
@@ -53,8 +53,6 @@ public interface LenientConfiguration {
     /**
      * Gets successfully resolved artifacts for dependencies that match given dependency spec.
      *
-     * TODO SF We need to pair and model the successfully and unsuccessfully resolved artifacts much better.
-     *
      * @param dependencySpec dependency spec
      * @return successfully resolved artifacts for dependencies that match given dependency spec
      */
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
index d304428..332886c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionSelector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionSelector.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.artifacts;
 
+import org.gradle.api.Incubating;
+
 /**
  * Selects a module version
  */
@@ -41,4 +43,15 @@ public interface ModuleVersionSelector {
      * @return module version
      */
     String getVersion();
+
+    /**
+     * To match strictly means that the given identifier needs to have
+     * equal group, module name and version.
+     * It does not smartly match dynamic versions,
+     * e.g. '1.+' selector does not strictly match '1.2' identifier.
+     *
+     * @return if this selector matches exactly the given identifier.
+     */
+    @Incubating
+    boolean matchesStrictly(ModuleVersionIdentifier identifier);
 }
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
index 041a53a..097cb95 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
@@ -17,6 +17,8 @@ package org.gradle.api.artifacts;
 
 import groovy.lang.Closure;
 import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.result.ResolutionResult;
 import org.gradle.api.file.FileCollection;
 
 /**
@@ -79,4 +81,13 @@ public interface ResolvableDependencies {
      * @param action The action to execute.
      */
     void afterResolve(Closure action);
+
+    /**
+     * Returns an instance of {@link org.gradle.api.artifacts.result.ResolutionResult}
+     * that gives access to the graph of the resolved dependencies.
+     *
+     * @return the resolution result
+     */
+    @Incubating
+    ResolutionResult getResolutionResult();
 }
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
index b86894a..636470b 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.artifacts.cache;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ArtifactIdentifier;
 
 import java.io.File;
@@ -23,6 +23,6 @@ import java.io.File;
 /**
  * Command methods for controlling artifact resolution via the DSL.
  */
- at Experimental
+ at Incubating
 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
index ce616ac..bd44899 100644
--- 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
@@ -15,13 +15,13 @@
  */
 package org.gradle.api.artifacts.cache;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
 /**
  * Command methods for controlling dependency resolution via the DSL.
  */
- at Experimental
+ at Incubating
 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
index 4cefe6b..d395a9f 100644
--- 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
@@ -15,14 +15,14 @@
  */
 package org.gradle.api.artifacts.cache;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 
 /**
  * Command methods for controlling module resolution via the DSL.
  */
- at Experimental
+ at Incubating
 public interface ModuleResolutionControl extends ResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> {
     // TODO: This should be part of the cached result?
     /**
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
index fa1a212..6543e01 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.artifacts.cache;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 
 import java.util.concurrent.TimeUnit;
 
@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
  * @param <A> The type of the request object for this resolution
  * @param <B> The type of the result of this resolution
  */
- at Experimental
+ at Incubating
 public interface ResolutionControl<A, B> {
     /**
      * Returns the query object that was requested in this resolution.
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
index ba3ba96..5593685 100644
--- 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
@@ -16,13 +16,13 @@
 package org.gradle.api.artifacts.cache;
 
 import org.gradle.api.Action;
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 
 /**
  * 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.
  */
- at Experimental
+ at Incubating
 public interface ResolutionRules {
     /**
      * Apply a rule to control resolution of dependencies.
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
index 3276f1e..88c9715 100644
--- 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
@@ -15,9 +15,12 @@
  */
 package org.gradle.api.artifacts.repositories;
 
+import org.gradle.api.internal.HasInternalProtocol;
+
 /**
  * A repository for resolving and publishing artifacts.
  */
+ at HasInternalProtocol
 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
@@ -32,6 +35,11 @@ public interface ArtifactRepository {
     /**
      * Sets the name for this repository.
      *
+     * If this repository is to be added to an {@link org.gradle.api.artifacts.ArtifactRepositoryContainer}
+     * (including {@link org.gradle.api.artifacts.dsl.RepositoryHandler}), its name should not be changed after it has
+     * been added to the container. This capability has been deprecated and is scheduled to be removed in the next major
+     * Gradle version.
+     *
      * @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/IvyArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
index 5763950..f5f4d3a 100644
--- 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
@@ -20,7 +20,16 @@ import groovy.lang.Closure;
 import java.net.URI;
 
 /**
- * An artifact repository which uses an Ivy format to store artifacts and meta-data.
+ * <p>An artifact repository which uses an Ivy format to store artifacts and meta-data</p>
+ *
+ * <p>When used to resolve metadata and artifact files, all available patterns will be searched.</p>
+ *
+ * <p>When used to upload metadata and artifact files, only a single, primary pattern will be used:
+ * <ol>
+ *     <li>If a URL is specified via {@link #setUrl} then that URL will be used for upload, combined with the applied {@link #layout(String)}.</li>
+ *     <li>If no URL has been specified but additional patterns have been added via {@link #artifactPattern} or {@link #ivyPattern}, then the first defined pattern will be used.</li>
+ * </ol>
+ * </p>
  */
 public interface IvyArtifactRepository extends ArtifactRepository, AuthenticationSupported {
 
@@ -48,14 +57,25 @@ public interface IvyArtifactRepository extends ArtifactRepository, Authenticatio
     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}.
+     * Adds an independent pattern that will be used to locate artifact files in this repository. This pattern will be used to locate ivy files as well, unless a specific
+     * ivy pattern is supplied via {@link #ivyPattern(String)}.
+     *
+     * If this pattern is not a fully-qualified URL, it will be interpreted as a file relative to the project directory.
+     * It is not interpreted relative the the URL specified in {@link #setUrl(Object)}.
+     *
+     * Patterns added in this way 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}.
+     * Adds an independent pattern that will be used to locate ivy files in this repository.
+     *
+     * If this pattern is not a fully-qualified URL, it will be interpreted as a file relative to the project directory.
+     * It is not interpreted relative the the URL specified in {@link #setUrl(Object)}.
+     *
+     * Patterns added in this way will be in addition to any layout-based patterns added via {@link #setUrl}.
      *
      * @param pattern The ivy pattern.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.java
new file mode 100644
index 0000000..bfb336b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/DependencyResult.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.api.artifacts.result;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+/**
+ * Represents the dependency result. An edge in the dependency graph. See also {@link ResolutionResult}.
+ */
+ at Incubating
+public interface DependencyResult {
+
+    /**
+     * Returns the requested module version.
+     *
+     * @return requested module version
+     */
+    ModuleVersionSelector getRequested();
+
+    /**
+     * Returns the resolved dependent module version result that
+     * lists this dependency result as a dependency.
+     *
+     * @return dependent resolved module version result
+     */
+    ResolvedModuleVersionResult getFrom();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.java
new file mode 100644
index 0000000..87192aa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ModuleVersionSelectionReason.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.api.artifacts.result;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Answers the question why given module version was selected during the dependency resolution
+ */
+ at Incubating
+public interface ModuleVersionSelectionReason {
+
+    /**
+     * Informs whether the module was forced.
+     * Users can force modules via {@link org.gradle.api.artifacts.ResolutionStrategy}
+     * or when declaring dependencies (see {@link org.gradle.api.artifacts.dsl.DependencyHandler}).
+     */
+    boolean isForced();
+
+    /**
+     * Informs whether the module was selected by conflict resolution.
+     * For more information about Gradle's conflict resolution please refer to the user
+     * guide. {@link org.gradle.api.artifacts.ResolutionStrategy} contains information
+     * about conflict resolution and includes means to configure it.
+     */
+    boolean isConflictResolution();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolutionResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolutionResult.java
new file mode 100644
index 0000000..b8f009d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolutionResult.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.api.artifacts.result;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+
+import java.util.Set;
+
+/**
+ * Contains the information about the resolution result.
+ * Gives access to the resolved dependency graph.
+ * In future it will contain more convenience methods and
+ * other useful information about the resolution results.
+ */
+ at Incubating
+public interface ResolutionResult {
+
+    /**
+     * Gives access to the resolved dependency graph.
+     * You can walk the graph recursively from the root to obtain information about resolved dependencies.
+     * For example, Gradle's built-in 'dependencies' uses it to render the dependency tree.
+     *
+     * @return the root node of the resolved dependency graph
+     */
+    ResolvedModuleVersionResult getRoot();
+
+    /**
+     * Retrieves all dependencies, including unresolved dependencies.
+     * Resolved dependencies are represented by instances of {@link ResolvedDependencyResult},
+     * unresolved dependencies by {@link UnresolvedDependencyResult}.
+     *
+     * In dependency graph terminology, this method returns the edges of the graph.
+     *
+     * @return all dependencies, including unresolved dependencies.
+     */
+    Set<? extends DependencyResult> getAllDependencies();
+
+    /**
+     * Applies given action for each dependency.
+     * An instance of {@link DependencyResult} is passed as parameter to the action.
+     *
+     * @param action - action that is applied for each dependency
+     */
+    void allDependencies(Action<? super DependencyResult> action);
+
+    /**
+     * Applies given closure for each dependency.
+     * An instance of {@link DependencyResult} is passed as parameter to the closure.
+     *
+     * @param closure - closure that is applied for each dependency
+     */
+    void allDependencies(Closure closure);
+
+    /**
+     * Retrieves all instances of {@link ResolvedModuleVersionResult} from the graph,
+     * e.g. all nodes of the dependency graph.
+     *
+     * @return all nodes of the dependency graph.
+     */
+    Set<ResolvedModuleVersionResult> getAllModuleVersions();
+
+    /**
+     * Applies given action for each module.
+     * An instance of {@link ResolvedModuleVersionResult} is passed as parameter to the action.
+     *
+     * @param action - action that is applied for each module
+     */
+    void allModuleVersions(Action<? super ResolvedModuleVersionResult> action);
+
+    /**
+     * Applies given closure for each module.
+     * An instance of {@link ResolvedModuleVersionResult} is passed as parameter to the closure.
+     *
+     * @param closure - closure that is applied for each module
+     */
+    void allModuleVersions(Closure closure);
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.java
new file mode 100644
index 0000000..ed8d420
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedDependencyResult.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.artifacts.result;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Resolved dependency result is an edge in the resolved dependency graph.
+ * Provides information about the requested module version and the selected module version.
+ * Requested differs from selected due to number of factors,
+ * for example conflict resolution, forcing particular version or when dynamic versions are used.
+ * For information about those terms please refer to the user guide.
+ */
+ at Incubating
+public interface ResolvedDependencyResult extends DependencyResult {
+
+    /**
+     * Returns the selected module version.
+     *
+     * @return selected module version
+     */
+    ResolvedModuleVersionResult getSelected();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.java
new file mode 100644
index 0000000..4c1fdd5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/ResolvedModuleVersionResult.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.artifacts.result;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.Set;
+
+/**
+ * Resolved module version result is a node in the resolved dependency graph.
+ * Contains the identifier of the module and the dependencies.
+ */
+ at Incubating
+public interface ResolvedModuleVersionResult {
+
+    /**
+     * The identifier of the resolved module.
+     *
+     * @return identifier
+     */
+    ModuleVersionIdentifier getId();
+
+    /**
+     * The dependencies of the resolved module. See {@link DependencyResult}.
+     * Includes resolved and unresolved dependencies (if any).
+     *
+     * @return dependencies
+     */
+    Set<? extends DependencyResult> getDependencies();
+
+    /**
+     * The dependents of the resolved module. See {@link ResolvedDependencyResult}.
+     *
+     * @return dependents
+     */
+    Set<? extends ResolvedDependencyResult> getDependents();
+
+    /**
+     * Informs why this module version was selected.
+     * Useful information if during the dependency resolution multiple candidate versions were found
+     * and one of them was selected as a part of conflict resolution.
+     * Informs if a version was forced during the resolution process.
+     * See {@link ModuleVersionSelectionReason}
+     *
+     * @return information why this module version was selected.
+     */
+    ModuleVersionSelectionReason getSelectionReason();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.java
new file mode 100644
index 0000000..fafeabd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/UnresolvedDependencyResult.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.artifacts.result;
+
+import org.gradle.api.Incubating;
+
+/**
+ * Unresolved dependency result
+ */
+ at Incubating
+public interface UnresolvedDependencyResult extends DependencyResult {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/package-info.java
new file mode 100644
index 0000000..dee4302
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/result/package-info.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.
+ */
+
+/**
+ * Classes that compose the resolution result
+ */
+package org.gradle.api.artifacts.result;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java b/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
index 43632b4..7fa733f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
@@ -16,7 +16,7 @@
 package org.gradle.api.file;
 
 import org.apache.commons.lang.StringUtils;
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.io.Serializable;
@@ -73,7 +73,7 @@ public class RelativePath implements Serializable {
     }
 
     public String getPathString() {
-        return GUtil.join(segments, "/");
+        return CollectionUtils.join("/", segments);
     }
 
     public File getFile(File baseDir) {
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 a0b79dd..897c0a7 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
@@ -19,6 +19,9 @@ package org.gradle.api.internal;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import groovy.lang.*;
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.codehaus.groovy.reflection.CachedClass;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.plugins.ExtensionAware;
@@ -30,7 +33,7 @@ import java.lang.reflect.Modifier;
 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>>();
+    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) {
         Instantiator instantiator = new DirectInstantiator();
@@ -38,14 +41,17 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
     }
 
     public <T> Class<? extends T> generate(Class<T> type) {
-        Map<Class, Class> cache = GENERATED_CLASSES.get(getClass());
+        Map<Class<?>, Class<?>> cache = GENERATED_CLASSES.get(getClass());
         if (cache == null) {
-            cache = new HashMap<Class, Class>();
+            // WeakHashMap won't work here. It keeps a strong reference to the mapping value, which is the generated class in this case
+            // However, the generated class has a strong reference to the source class (it extends it), so the keys will always be
+            // strongly reachable while this Class is strongly reachable. Use weak references for both key and value of the mapping instead.
+            cache = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK);
             GENERATED_CLASSES.put(getClass(), cache);
         }
-        Class generatedClass = cache.get(type);
+        Class<?> generatedClass = cache.get(type);
         if (generatedClass != null) {
-            return generatedClass;
+            return generatedClass.asSubclass(type);
         }
 
         if (Modifier.isPrivate(type.getModifiers())) {
@@ -143,19 +149,27 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
                 if (method.isPrivate()) {
                     continue;
                 }
-                if (method.getParameterTypes().length != 1) {
+                CachedClass[] parameterTypes = method.getParameterTypes();
+                if (parameterTypes.length == 0) {
                     continue;
                 }
                 methods.put(method.getName(), method);
-                if (method.getParameterTypes()[0].getTheClass().equals(Action.class)) {
+
+                CachedClass lastParameter = parameterTypes[parameterTypes.length - 1];
+                if (lastParameter.getTheClass().equals(Action.class)) {
                     actionMethods.add(method);
                 }
             }
 
             for (MetaMethod method : actionMethods) {
                 boolean hasClosure = false;
+                Class[] actionMethodParameterTypes = method.getNativeParameterTypes();
+                int numParams = actionMethodParameterTypes.length;
+                Class[] closureMethodParameterTypes = new Class[actionMethodParameterTypes.length];
+                System.arraycopy(actionMethodParameterTypes, 0, closureMethodParameterTypes, 0, actionMethodParameterTypes.length);
+                closureMethodParameterTypes[numParams - 1] = Closure.class;
                 for (MetaMethod otherMethod : methods.get(method.getName())) {
-                    if (otherMethod.getParameterTypes()[0].getTheClass().equals(Closure.class)) {
+                    if (Arrays.equals(otherMethod.getNativeParameterTypes(), closureMethodParameterTypes)) {
                         hasClosure = true;
                         break;
                     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java
index c488693..964e691 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java
@@ -41,7 +41,7 @@ public abstract class AbstractDynamicObject implements DynamicObject {
 
     protected MissingPropertyException propertyMissingException(String name) {
         throw new MissingPropertyException(String.format("Could not find property '%s' on %s.", name,
-                getDisplayName()));
+                getDisplayName()), name, null);
     }
 
     public Map<String, ?> getProperties() {
@@ -56,6 +56,14 @@ public abstract class AbstractDynamicObject implements DynamicObject {
         throw methodMissingException(name, arguments);
     }
 
+    public boolean isMayImplementMissingMethods() {
+        return false;
+    }
+
+    public boolean isMayImplementMissingProperties() {
+        return false;
+    }
+
     protected groovy.lang.MissingMethodException methodMissingException(String name, Object... params) {
         return new MissingMethodException(this, getDisplayName(), name, params);
     }
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 c35cbbb..0dcb801 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,20 +16,26 @@
 package org.gradle.api.internal;
 
 import groovy.lang.*;
-import org.gradle.api.Action;
+import org.gradle.api.Transformer;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtensionAware;
 import org.gradle.internal.reflect.JavaReflectionUtil;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.JavaMethod;
 import org.objectweb.asm.*;
+import org.objectweb.asm.Type;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.*;
 import java.util.ArrayList;
 import java.util.List;
 
 public class AsmBackedClassGenerator extends AbstractClassGenerator {
+    private static final JavaMethod<ClassLoader, Class> DEFINE_CLASS_METHOD = JavaMethod.create(ClassLoader.class, Class.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
+
     @Override
     protected <T> ClassBuilder<T> start(Class<T> type) {
         return new ClassBuilderImpl<T>(type);
@@ -83,8 +89,21 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             }
             String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, paramTypes.toArray(
                     new Type[paramTypes.size()]));
-            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", methodDescriptor, null,
+
+            String signature = signature(constructor);
+
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", methodDescriptor, signature,
                     new String[0]);
+
+            for (Annotation annotation : constructor.getDeclaredAnnotations()) {
+                if (annotation.annotationType().getAnnotation(Inherited.class) != null) {
+                    continue;
+                }
+                Retention retention = annotation.annotationType().getAnnotation(Retention.class);
+                AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotation(Type.getType(annotation.annotationType()).getDescriptor(), retention != null && retention.value() == RetentionPolicy.RUNTIME);
+                annotationVisitor.visitEnd();
+            }
+
             methodVisitor.visitCode();
 
             // this.super(p0 .. pn)
@@ -110,6 +129,101 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             methodVisitor.visitEnd();
         }
 
+        /**
+         * Generates the signature for the given constructor
+         */
+        private String signature(Constructor<?> constructor) {
+            StringBuilder builder = new StringBuilder();
+            if (constructor.getTypeParameters().length > 0) {
+                builder.append('<');
+                for (TypeVariable<?> typeVariable : constructor.getTypeParameters()) {
+                    builder.append(typeVariable.getName());
+                    for (java.lang.reflect.Type bound : typeVariable.getBounds()) {
+                        builder.append(':');
+                        visitType(bound, builder);
+                    }
+                }
+                builder.append('>');
+            }
+            builder.append('(');
+            for (java.lang.reflect.Type paramType : constructor.getGenericParameterTypes()) {
+                visitType(paramType, builder);
+            }
+            builder.append(")V");
+            for (java.lang.reflect.Type exceptionType : constructor.getGenericExceptionTypes()) {
+                builder.append('^');
+                visitType(exceptionType, builder);
+            }
+            return builder.toString();
+        }
+
+        private void visitType(java.lang.reflect.Type type, StringBuilder builder) {
+            if (type instanceof Class) {
+                Class<?> cl = (Class<?>) type;
+                if (cl.isPrimitive()) {
+                    builder.append(Type.getType(cl).getDescriptor());
+                } else {
+                    if (cl.isArray()) {
+                        builder.append(cl.getName().replace('.', '/'));
+                    } else {
+                        builder.append('L');
+                        builder.append(cl.getName().replace('.', '/'));
+                        builder.append(';');
+                    }
+                }
+            } else if (type instanceof ParameterizedType) {
+                ParameterizedType parameterizedType = (ParameterizedType) type;
+                visitNested(parameterizedType.getRawType(), builder);
+                builder.append('<');
+                for (java.lang.reflect.Type param : parameterizedType.getActualTypeArguments()) {
+                    visitType(param, builder);
+                }
+                builder.append(">;");
+            } else if (type instanceof WildcardType) {
+                WildcardType wildcardType = (WildcardType) type;
+                if (wildcardType.getUpperBounds().length == 1 && wildcardType.getUpperBounds()[0].equals(Object.class)) {
+                    if (wildcardType.getLowerBounds().length == 0) {
+                        builder.append('*');
+                        return;
+                    }
+                } else {
+                    for (java.lang.reflect.Type upperType : wildcardType.getUpperBounds()) {
+                        builder.append('+');
+                        visitType(upperType, builder);
+                    }
+                }
+                for (java.lang.reflect.Type lowerType : wildcardType.getLowerBounds()) {
+                    builder.append('-');
+                    visitType(lowerType, builder);
+                }
+            } else if (type instanceof TypeVariable) {
+                TypeVariable<?> typeVar = (TypeVariable) type;
+                builder.append('T');
+                builder.append(typeVar.getName());
+                builder.append(';');
+            } else if (type instanceof GenericArrayType) {
+                GenericArrayType arrayType = (GenericArrayType) type;
+                builder.append('[');
+                visitType(arrayType.getGenericComponentType(), builder);
+            } else {
+                throw new IllegalArgumentException(String.format("Cannot generate signature for %s.", type));
+            }
+        }
+
+        private void visitNested(java.lang.reflect.Type type, StringBuilder builder) {
+            if (type instanceof Class) {
+                Class<?> cl = (Class<?>) type;
+                if (cl.isPrimitive()) {
+                    builder.append(Type.getType(cl).getDescriptor());
+                } else {
+                    builder.append('L');
+                    builder.append(cl.getName().replace('.', '/'));
+                }
+            } else {
+                visitType(type, builder);
+            }
+        }
+
         public void mixInDynamicAware() throws Exception {
             final Type helperType = Type.getType(MixInExtensibleDynamicObject.class);
 
@@ -659,23 +773,38 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             Type actionImplType = Type.getType(ClosureBackedAction.class);
             Type closureType = Type.getType(Closure.class);
 
-            String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{closureType});
+            Type[] originalParameterTypes = CollectionUtils.collectArray(method.getNativeParameterTypes(), Type.class, new Transformer<Type, Class>() {
+                public Type transform(Class clazz) {
+                    return Type.getType(clazz);
+                }
+            });
+            int numParams = originalParameterTypes.length;
+            Type[] closurisedParameterTypes = new Type[numParams];
+            System.arraycopy(originalParameterTypes, 0, closurisedParameterTypes, 0, numParams);
+            closurisedParameterTypes[numParams - 1] = closureType;
+
+            String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, closurisedParameterTypes);
 
-            // GENERATE public void <method>(Closure v) { <method>(new ClosureBackedAction(v)); }
+            // 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));
+            // GENERATE <method>(…, new ClosureBackedAction(v));
             methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
 
+            for (int stackVar = 1; stackVar < numParams; ++stackVar) {
+                methodVisitor.visitVarInsn(closurisedParameterTypes[stackVar - 1].getOpcode(Opcodes.ILOAD), stackVar);
+            }
+
             // GENERATE new ClosureBackedAction(v);
             methodVisitor.visitTypeInsn(Opcodes.NEW, actionImplType.getInternalName());
             methodVisitor.visitInsn(Opcodes.DUP);
-            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, numParams);
             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())});
+
+            methodDescriptor = Type.getMethodDescriptor(Type.getType(method.getReturnType()), originalParameterTypes);
             methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), method.getName(), methodDescriptor);
 
             methodVisitor.visitInsn(Opcodes.RETURN);
@@ -687,9 +816,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             visitor.visitEnd();
 
             byte[] bytecode = visitor.toByteArray();
-            return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
-                    typeName, bytecode, 0, bytecode.length
-            });
+            return DEFINE_CLASS_METHOD.invoke(type.getClassLoader(), typeName, bytecode, 0, bytecode.length);
         }
     }
 
@@ -711,15 +838,4 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
     }
 
-    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/BeanDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
index d966399..01b2bc3 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
@@ -32,14 +32,20 @@ public class BeanDynamicObject extends AbstractDynamicObject {
     private final Object bean;
     private final boolean includeProperties;
     private final DynamicObject delegate;
+    private final boolean implementsMissing;
 
     public BeanDynamicObject(Object bean) {
         this(bean, true);
     }
 
     private BeanDynamicObject(Object bean, boolean includeProperties) {
+        this(bean, includeProperties, true);
+    }
+
+    private BeanDynamicObject(Object bean, boolean includeProperties, boolean implementsMissing) {
         this.bean = bean;
         this.includeProperties = includeProperties;
+        this.implementsMissing = implementsMissing;
         this.delegate = determineDelegate(bean);
     }
 
@@ -55,6 +61,10 @@ public class BeanDynamicObject extends AbstractDynamicObject {
         return new BeanDynamicObject(bean, false);
     }
 
+    public BeanDynamicObject withNotImplementsMissing() {
+        return new BeanDynamicObject(bean, includeProperties, false);
+    }
+
     @Override
     public String toString() {
         return getDisplayName();
@@ -73,6 +83,16 @@ public class BeanDynamicObject extends AbstractDynamicObject {
     }
 
     @Override
+    public boolean isMayImplementMissingMethods() {
+        return implementsMissing && delegate.isMayImplementMissingMethods();
+    }
+
+    @Override
+    public boolean isMayImplementMissingProperties() {
+        return implementsMissing && includeProperties && delegate.isMayImplementMissingProperties();
+    }
+
+    @Override
     public boolean hasProperty(String name) {
         return delegate.hasProperty(name);    
     }
@@ -203,6 +223,14 @@ public class BeanDynamicObject extends AbstractDynamicObject {
                 throw methodMissingException(name, arguments);
             }
         }
+
+        public boolean isMayImplementMissingMethods() {
+            return true;
+        }
+
+        public boolean isMayImplementMissingProperties() {
+            return true;
+        }
     }
 
     /*
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.java
new file mode 100644
index 0000000..2c1e920
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClosureBackedAction.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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidActionClosureException;
+import org.gradle.util.Configurable;
+
+public class ClosureBackedAction<T> implements Action<T> {
+    private final Closure closure;
+    private final boolean configureableAware;
+    private final int resolveStrategy;
+
+    public ClosureBackedAction(Closure closure) {
+        this(closure, Closure.DELEGATE_FIRST, true);
+    }
+
+    public ClosureBackedAction(Closure closure, int resolveStrategy) {
+        this(closure, resolveStrategy, false);
+    }
+
+    public ClosureBackedAction(Closure closure, int resolveStrategy, boolean configureableAware) {
+        this.closure = closure;
+        this.configureableAware = configureableAware;
+        this.resolveStrategy = resolveStrategy;
+    }
+
+    public void execute(T delegate) {
+        if (closure == null) {
+            return;
+        }
+
+        try {
+            if (configureableAware && delegate instanceof Configurable) {
+                ((Configurable)delegate).configure(closure);
+            } else {
+                Closure copy = (Closure) closure.clone();
+                copy.setResolveStrategy(resolveStrategy);
+                copy.setDelegate(delegate);
+                if (copy.getMaximumNumberOfParameters() == 0) {
+                    copy.call();
+                } else {
+                    copy.call(delegate);
+                }
+            }
+        } catch (groovy.lang.MissingMethodException e) {
+            if (e.getType().equals(closure.getClass()) && e.getMethod().equals("doCall")) {
+                throw new InvalidActionClosureException(closure, delegate);
+            }
+        }
+    }
+
+}
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
index a6483c7..9e8f4de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDomainObjectSet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDomainObjectSet.java
@@ -15,14 +15,14 @@
  */
 package org.gradle.api.internal;
 
+import org.apache.commons.collections.collection.CompositeCollection;
 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;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 
 /**
  * A domain object collection that presents a combined view of one or more collections.
@@ -35,6 +35,7 @@ public class CompositeDomainObjectSet<T> extends DefaultDomainObjectSet<T> {
     private Spec<T> notInSpec = new ItemNotInCompositeSpec();
     
     public CompositeDomainObjectSet(Class<T> type) {
+        //noinspection unchecked
         super(type, new CompositeCollection());
     }
 
@@ -66,16 +67,17 @@ public class CompositeDomainObjectSet<T> extends DefaultDomainObjectSet<T> {
         }
     }
 
+    @SuppressWarnings("unchecked")
     protected CompositeCollection getStore() {
         return (CompositeCollection)super.getStore();
     }
 
     public Action<? super T> whenObjectAdded(Action<? super T> action) {
-        return super.whenObjectAdded(new FilteredAction<T>(uniqueSpec, action));
+        return super.whenObjectAdded(Actions.<T>filter(action, uniqueSpec));
     }
 
     public Action<? super T> whenObjectRemoved(Action<? super T> action) {
-        return super.whenObjectRemoved(new FilteredAction<T>(notInSpec, action));
+        return super.whenObjectRemoved(Actions.<T>filter(action, notInSpec));
     }
     
     public void addCollection(DomainObjectCollection<? extends T> collection) {
@@ -86,17 +88,19 @@ public class CompositeDomainObjectSet<T> extends DefaultDomainObjectSet<T> {
 
     public void removeCollection(DomainObjectCollection<? extends T> collection) {
         getStore().removeComposited(collection);
-        Action<T> action = getEventRegister().getRemoveAction();
+        Action<? super T> action = getEventRegister().getRemoveAction();
         for (T item : collection) {
             action.execute(item);
         }
     }
 
     public Iterator<T> iterator() {
+        //noinspection unchecked
         return new LinkedHashSet<T>(getStore()).iterator();
     }
 
     public int size() {
+        //noinspection unchecked
         return new LinkedHashSet<T>(getStore()).size();
     }
     
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
index 8b68663..33032f0 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
@@ -19,6 +19,7 @@ import groovy.lang.Closure;
 import groovy.lang.MissingMethodException;
 import groovy.lang.MissingPropertyException;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -41,6 +42,26 @@ public abstract class CompositeDynamicObject extends AbstractDynamicObject {
     }
 
     @Override
+    public boolean isMayImplementMissingMethods() {
+        for (DynamicObject object : objects) {
+            if (object.isMayImplementMissingMethods()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isMayImplementMissingProperties() {
+        for (DynamicObject object : objects) {
+            if (object.isMayImplementMissingProperties()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
     public boolean hasProperty(String name) {
         for (DynamicObject object : objects) {
             if (object.hasProperty(name)) {
@@ -57,6 +78,19 @@ public abstract class CompositeDynamicObject extends AbstractDynamicObject {
                 return object.getProperty(name);
             }
         }
+
+        for (DynamicObject object : objects) {
+            if (object.isMayImplementMissingProperties()) {
+                try {
+                    return object.getProperty(name);
+                } catch (MissingPropertyException e) {
+                    if (e.getProperty() == null || !e.getProperty().equals(name)) {
+                        throw e;
+                    }
+                }
+            }
+        }
+
         return super.getProperty(name);
     }
 
@@ -68,6 +102,20 @@ public abstract class CompositeDynamicObject extends AbstractDynamicObject {
                 return;
             }
         }
+
+        for (DynamicObject object : updateObjects) {
+            if (object.isMayImplementMissingProperties()) {
+                try {
+                    object.setProperty(name, value);
+                    return;
+                } catch (MissingPropertyException e) {
+                    if (e.getProperty() == null || !e.getProperty().equals(name)) {
+                        throw e;
+                    }
+                }
+            }
+        }
+
         updateObjects[updateObjects.length - 1].setProperty(name, value);
     }
 
@@ -109,6 +157,18 @@ public abstract class CompositeDynamicObject extends AbstractDynamicObject {
             }
         }
 
+        for (DynamicObject object : objects) {
+            if (object.isMayImplementMissingMethods()) {
+                try {
+                    return object.invokeMethod(name, arguments);
+                } catch (MissingMethodException e) {
+                    if (e.isStatic() || !e.getMethod().equals(name) || !Arrays.equals(e.getArguments(), arguments)) {
+                        throw e;
+                    }
+                }
+            }
+        }
+
         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
index df0715d..98b1285 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
@@ -24,7 +24,7 @@ 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 Action<? super String> onMissing;
     private final ThreadLocal<Boolean> configuring = new ThreadLocal<Boolean>() {
         @Override
         protected Boolean initialValue() {
@@ -32,14 +32,11 @@ public class ConfigureDelegate extends GroovyObjectSupport {
         }
     };
 
-    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);
+        this(owner, delegate, Actions.doNothing());
     }
-    public ConfigureDelegate(Object owner, Object delegate, Action<String> onMissing) {
+
+    public ConfigureDelegate(Object owner, Object delegate, Action<? super String> onMissing) {
         this.owner = DynamicObjectUtil.asDynamicObject(owner);
         this.delegate = DynamicObjectUtil.asDynamicObject(delegate);
         this.onMissing = onMissing;
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
index 59dde19..12ed220 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectCollection.java
@@ -23,7 +23,6 @@ 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.*;
 
@@ -152,11 +151,7 @@ public class DefaultDomainObjectCollection<T> extends AbstractCollection<T> impl
     }
 
     private Action<? super T> toAction(final Closure action) {
-        return new Action<T>() {
-            public void execute(T t) {
-                ConfigureUtil.configure(action, t);
-            }
-        };
+        return new ClosureBackedAction<T>(action);
     }
 
     public boolean add(T toAdd) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyInjectingInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyInjectingInstantiator.java
new file mode 100644
index 0000000..168b46f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyInjectingInstantiator.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.internal.reflect.Instantiator;
+import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.internal.reflect.ObjectInstantiationException;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.util.DeprecationLogger;
+
+import javax.inject.Inject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An {@link Instantiator} that applies JSR-330 style dependency injection.
+ */
+public class DependencyInjectingInstantiator implements Instantiator {
+    private final ServiceRegistry services;
+    private final Action<String> onDeprecationWarning;
+
+    public DependencyInjectingInstantiator(ServiceRegistry services) {
+        this.services = services;
+        onDeprecationWarning = new Action<String>() {
+            public void execute(String message) {
+                DeprecationLogger.nagUserWith(message);
+            }
+        };
+    }
+
+    DependencyInjectingInstantiator(ServiceRegistry services, Action<String> onDeprecationWarning) {
+        this.services = services;
+        this.onDeprecationWarning = onDeprecationWarning;
+    }
+
+    public <T> T newInstance(Class<T> type, Object... parameters) {
+        try {
+            validateType(type);
+            Constructor<?> constructor = selectConstructor(type, parameters);
+            constructor.setAccessible(true);
+            Object[] resolvedParameters = convertParameters(type, constructor, parameters);
+            try {
+                return type.cast(constructor.newInstance(resolvedParameters));
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        } catch (Throwable e) {
+            throw new ObjectInstantiationException(type, e);
+        }
+    }
+
+    private <T> Object[] convertParameters(Class<T> type, Constructor<?> match, Object[] parameters) {
+        Class<?>[] parameterTypes = match.getParameterTypes();
+        if (parameterTypes.length < parameters.length) {
+            throw new IllegalArgumentException(String.format("Too many parameters provided for constructor for class %s. Expected %s, received %s.", type.getName(), parameterTypes.length, parameters.length));
+        }
+        Object[] resolvedParameters = new Object[parameterTypes.length];
+        int pos = 0;
+        for (int i = 0; i < resolvedParameters.length; i++) {
+            Class<?> targetType = parameterTypes[i];
+            if (targetType.isPrimitive()) {
+                targetType = JavaReflectionUtil.getWrapperTypeForPrimitiveType(targetType);
+            }
+            if (pos < parameters.length && targetType.isInstance(parameters[pos])) {
+                resolvedParameters[i] = parameters[pos];
+                pos++;
+            } else {
+                resolvedParameters[i] = services.get(match.getGenericParameterTypes()[i]);
+            }
+        }
+        if (pos != parameters.length) {
+            throw new IllegalArgumentException(String.format("Unexpected parameter provided for constructor for class %s.", type.getName()));
+        }
+        return resolvedParameters;
+    }
+
+    private <T> Constructor<?> selectConstructor(Class<T> type, Object... parameters) {
+        Constructor<?>[] constructors = type.getDeclaredConstructors();
+
+        // For backwards compatibility, first we look for a public constructor that accepts the provided parameters
+        // Then we find a candidate constructor as per JSR-330
+        // If we find an old style match, then warn if this is not the same as the JSR-330 match. Use the old style match
+        // If we find a JSR-330 match and no old style match, use the JSR-330 match
+        // Otherwise, fail
+
+        Constructor<?> defaultConstructor = null;
+        List<Constructor<?>> injectConstructors = new ArrayList<Constructor<?>>();
+        for (Constructor<?> constructor : constructors) {
+            if (constructor.getAnnotation(Inject.class) != null) {
+                injectConstructors.add(constructor);
+            }
+            if (constructor.getParameterTypes().length == 0) {
+                defaultConstructor = constructor;
+            }
+        }
+        if (injectConstructors.isEmpty() && constructors.length == 1 && defaultConstructor != null) {
+            injectConstructors.add(defaultConstructor);
+        }
+
+        Constructor<?> parameterMatchConstructor = null;
+        for (Constructor<?> constructor : type.getConstructors()) {
+            Class<?>[] parameterTypes = constructor.getParameterTypes();
+            if (parameterTypes.length == parameters.length) {
+                boolean match = true;
+                for (int i = 0; match && i < parameters.length; i++) {
+                    Class<?> targetType = parameterTypes[i];
+                    if (targetType.isPrimitive()) {
+                        targetType = JavaReflectionUtil.getWrapperTypeForPrimitiveType(targetType);
+                    }
+                    if (!targetType.isInstance(parameters[i])) {
+                        match = false;
+                    }
+                }
+                if (match) {
+                    if (parameterMatchConstructor != null) {
+                        throw new IllegalArgumentException(String.format("Class %s has multiple constructors that accept parameters %s.", type.getName(), Arrays.toString(parameters)));
+                    }
+                    parameterMatchConstructor = constructor;
+                }
+            }
+        }
+
+        if (parameterMatchConstructor == null && type.getConstructors().length == 1) {
+            // No match - allow a single constructor
+            parameterMatchConstructor = type.getConstructors()[0];
+        }
+
+        if (parameterMatchConstructor == null) {
+            // Use JSR-330 semantics
+            if (injectConstructors.isEmpty()) {
+                throw new IllegalArgumentException(String.format("Class %s has no constructor that accepts parameters %s or that is annotated with @Inject.", type.getName(), Arrays.toString(parameters)));
+            }
+            if (injectConstructors.size() > 1) {
+                throw new IllegalArgumentException(String.format("Class %s has multiple constructors with @Inject annotation.", type.getName()));
+            }
+            return injectConstructors.get(0);
+        }
+
+        // Use backwards compatible semantics, but warn when they don't match
+
+        if (injectConstructors.isEmpty()) {
+            if (type.getConstructors().length == 1) {
+                onDeprecationWarning.execute(String.format("Constructor for class %s is not annotated with @Inject. In Gradle 2.0 this will be treated as an error.", type.getName()));
+            } else {
+                onDeprecationWarning.execute(String.format("Class %s has multiple constructors and no constructor is annotated with @Inject. In Gradle 2.0 this will be treated as an error.", type.getName()));
+            }
+        } else if (injectConstructors.size() > 1) {
+            onDeprecationWarning.execute(String.format("Class %s has multiple constructors with @Inject annotation. In Gradle 2.0 this will be treated as an error.", type.getName()));
+        } else if (!injectConstructors.get(0).equals(parameterMatchConstructor)) {
+            onDeprecationWarning.execute(String.format("Class %s has @Inject annotation on an unexpected constructor. In Gradle 2.0 the constructor annotated with @Inject will be used instead of the current default constructor.", type.getName()));
+        }
+        return parameterMatchConstructor;
+    }
+
+    private <T> void validateType(Class<T> type) {
+        if (type.isInterface() || type.isAnnotation() || type.isEnum()) {
+            throw new IllegalArgumentException(String.format("Type %s is not a class.", type.getName()));
+        }
+        if (type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers())) {
+            throw new IllegalArgumentException(String.format("Class %s is a non-static inner class.", type.getName()));
+        }
+        if (Modifier.isAbstract(type.getModifiers())) {
+            throw new IllegalArgumentException(String.format("Class %s is an abstract class.", type.getName()));
+        }
+    }
+}
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
index db52f8f..ba7d428 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DocumentationRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DocumentationRegistry.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal;
 
+import org.gradle.util.GradleVersion;
+
 import java.io.File;
 
 /**
@@ -23,9 +25,15 @@ import java.io.File;
  */
 public class DocumentationRegistry {
     private final GradleDistributionLocator locator;
+    private final GradleVersion gradleVersion;
 
     public DocumentationRegistry(GradleDistributionLocator locator) {
+        this(locator, GradleVersion.current());
+    }
+
+    public DocumentationRegistry(GradleDistributionLocator locator, GradleVersion gradleVersion) {
         this.locator = locator;
+        this.gradleVersion = gradleVersion;
     }
 
     /**
@@ -45,6 +53,10 @@ public class DocumentationRegistry {
                 throw new IllegalArgumentException(String.format("User guide page '%s' not found.", userGuideLocation));
             }
         }
-        return String.format("http://gradle.org/docs/current/userguide/%s.html", id);
+        return String.format("http://gradle.org/docs/%s/userguide/%s.html", gradleVersion.getVersion(), id);
+    }
+
+    public String getFeatureLifecycle() {
+        return getDocumentationFor("feature_lifecycle");
     }
 }
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 10306e0..24df4a5 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
@@ -39,4 +39,12 @@ public interface DynamicObject {
     boolean hasMethod(String name, Object... arguments);
 
     Object invokeMethod(String name, Object... arguments) throws MissingMethodException;
+
+    /**
+     * Used to indicate that the dynamic object may still be able to invoke the method, regardless of {@link #hasMethod(String, Object...)}
+     */
+    boolean isMayImplementMissingMethods();
+
+    boolean isMayImplementMissingProperties();
+
 }
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 5f02139..abf053d 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
@@ -49,4 +49,12 @@ public class DynamicObjectHelper implements DynamicObject {
     public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
         throw new UnsupportedOperationException();
     }
+
+    public boolean isMayImplementMissingMethods() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isMayImplementMissingProperties() {
+        throw new UnsupportedOperationException();
+    }
 }
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
index b20c4da..3ecdd73 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicPropertyNamer.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicPropertyNamer.groovy
@@ -44,17 +44,4 @@ class DynamicPropertyNamer implements Namer<Object> {
         
         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/ExtensibleDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
index 8549ddf..102ec3f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
@@ -189,6 +189,14 @@ public class ExtensibleDynamicObject extends CompositeDynamicObject implements H
         public Object invokeMethod(String name, Object... arguments) {
             return snapshotInheritable().invokeMethod(name, arguments);
         }
+
+        public boolean isMayImplementMissingMethods() {
+            return snapshotInheritable().isMayImplementMissingMethods();
+        }
+
+        public boolean isMayImplementMissingProperties() {
+            return snapshotInheritable().isMayImplementMissingProperties();
+        }
     }
 
 }
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
deleted file mode 100644
index 54bf4bd..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/FilteredAction.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.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/NoNamingPropertyException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NoNamingPropertyException.java
new file mode 100644
index 0000000..5d5bca9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NoNamingPropertyException.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;
+
+public class NoNamingPropertyException extends RuntimeException {
+    NoNamingPropertyException(Object thing, String property) {
+        super(String.format("Unable to determine the name of '%s' because it does not have a '%s' property", thing, property));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NullNamingPropertyException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NullNamingPropertyException.java
new file mode 100644
index 0000000..d5cb411
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NullNamingPropertyException.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;
+
+public class NullNamingPropertyException extends RuntimeException {
+    NullNamingPropertyException(Object thing, String property) {
+        super(String.format("Unable to determine the name of '%s' because its value for the naming property '%s' is null", thing, property));
+    }
+}
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 7bd0bd1..4237340 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
@@ -21,7 +21,6 @@ import groovy.util.Node;
 import groovy.util.XmlNodePrinter;
 import groovy.util.XmlParser;
 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.XmlProvider;
@@ -56,7 +55,35 @@ public class XmlTransformer implements Transformer<String, String> {
     }
 
     public void addAction(Closure closure) {
-        actions.add((Action<XmlProvider>) DefaultGroovyMethods.asType(closure, Action.class));
+        actions.add(new ClosureBackedAction<XmlProvider>(closure));
+    }
+
+    public void transform(File destination, final String encoding, final Action<? super Writer> generator) {
+        IoActions.writeFile(destination, encoding, new Action<Writer>() {
+            public void execute(Writer writer) {
+                transform(writer, encoding, generator);
+            }
+        });
+    }
+
+    public void transform(File destination, final Action<? super Writer> generator) {
+        IoActions.writeFile(destination, new Action<Writer>() {
+            public void execute(Writer writer) {
+                transform(writer, generator);
+            }
+        });
+    }
+
+    public void transform(Writer destination, Action<? super Writer> generator) {
+        StringWriter stringWriter = new StringWriter();
+        generator.execute(stringWriter);
+        transform(stringWriter.toString(), destination);
+    }
+
+    public void transform(Writer destination, String encoding, Action<? super Writer> generator) {
+        StringWriter stringWriter = new StringWriter();
+        generator.execute(stringWriter);
+        transform(stringWriter.toString(), destination, encoding);
     }
 
     public String transform(String original) {
@@ -67,6 +94,10 @@ public class XmlTransformer implements Transformer<String, String> {
         doTransform(original).writeTo(destination);
     }
 
+    public void transform(String original, Writer destination, String encoding) {
+        doTransform(original).writeTo(destination, encoding);
+    }
+
     public void transform(String original, OutputStream destination) {
         doTransform(original).writeTo(destination);
     }
@@ -143,6 +174,10 @@ public class XmlTransformer implements Transformer<String, String> {
             doWriteTo(writer, null);
         }
 
+        public void writeTo(Writer writer, String encoding) {
+            doWriteTo(writer, encoding);
+        }
+
         public void writeTo(OutputStream stream) {
             try {
                 Writer writer = new OutputStreamWriter(stream, "UTF-8");
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
index dce7c55..874e154 100644
--- 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
@@ -16,12 +16,11 @@
 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;
+    ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException;
 }
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
index 4d98a2e..dc617f2 100644
--- 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
@@ -15,14 +15,18 @@
  */
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.XmlTransformer;
 
 import java.io.File;
+import java.util.Set;
 
 /**
  * @author Hans Dockter
  */
 public interface ArtifactPublisher {
-    void publish(ConfigurationInternal configuration, File descriptorDestination) throws PublishException;
+    void publish(Module module, Set<? extends Configuration> configurations, File descriptorDestination, @Nullable XmlTransformer descriptorTransformer) throws PublishException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.java
new file mode 100644
index 0000000..8b426b7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisherFactory.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;
+
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+public interface ArtifactPublisherFactory {
+
+    ArtifactPublisher createArtifactPublisher(ArtifactRepositoryInternal repository);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java
new file mode 100644
index 0000000..3a0e240
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.repositories.ArtifactRepository;
+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.artifacts.repositories.FixedResolverArtifactRepository;
+
+/**
+ * Factory for {@link ArtifactRepository} implementations.
+ *
+ * Differs from {@link org.gradle.api.internal.artifacts.dsl.RepositoryFactory} in that this is internal and does not provide
+ * API for configuring the repositories at creation time. {@link org.gradle.api.internal.artifacts.dsl.RepositoryFactory} is the DSL
+ * layer on top of this internal factory.
+ */
+public interface BaseRepositoryFactory {
+
+    ArtifactRepository createRepository(Object userDescription);
+
+    FlatDirectoryArtifactRepository createFlatDirRepository();
+
+    MavenArtifactRepository createMavenLocalRepository();
+
+    MavenArtifactRepository createMavenCentralRepository();
+
+    IvyArtifactRepository createIvyRepository();
+
+    MavenArtifactRepository createMavenRepository();
+
+    DependencyResolver toResolver(ArtifactRepository repository);
+
+    FixedResolverArtifactRepository createResolverBackedRepository(DependencyResolver resolver);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.java
new file mode 100644
index 0000000..9ee7459
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactPublisherFactory.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;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is a temporary measure while we are having to deal with parallel publication mechanisms.
+ */
+public class DefaultArtifactPublisherFactory implements ArtifactPublisherFactory {
+
+    private final Transformer<ArtifactPublisher, ResolverProvider> providerToPublisherTransformer;
+
+    public DefaultArtifactPublisherFactory(Transformer<ArtifactPublisher, ResolverProvider> providerToPublisherTransformer) {
+        this.providerToPublisherTransformer = providerToPublisherTransformer;
+    }
+
+    public ArtifactPublisher createArtifactPublisher(final ArtifactRepositoryInternal repository) {
+        return providerToPublisherTransformer.transform(new ResolverProvider() {
+            public List<DependencyResolver> getResolvers() {
+                return Collections.singletonList(repository.createResolver());
+            }
+        });
+    }
+
+}
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
index 0a1b6b1..c5e9fb1 100644
--- 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
@@ -22,13 +22,12 @@ 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.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.internal.DefaultNamedDomainObjectList;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
-import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GUtil;
@@ -37,13 +36,15 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import static org.gradle.api.internal.Cast.cast;
+
 /**
  * @author Hans Dockter
  */
 public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObjectList<ArtifactRepository>
         implements ArtifactRepositoryContainer {
-    private final ResolverFactory resolverFactory;
 
+    private final BaseRepositoryFactory baseRepositoryFactory;
     private final Action<ArtifactRepository> addLastAction = new Action<ArtifactRepository>() {
         public void execute(ArtifactRepository repository) {
             DefaultArtifactRepositoryContainer.super.add(repository);
@@ -55,9 +56,9 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
         }
     };
 
-    public DefaultArtifactRepositoryContainer(ResolverFactory resolverFactory, Instantiator instantiator) {
+    public DefaultArtifactRepositoryContainer(BaseRepositoryFactory baseRepositoryFactory, Instantiator instantiator) {
         super(ArtifactRepository.class, instantiator, new RepositoryNamer());
-        this.resolverFactory = resolverFactory;
+        this.baseRepositoryFactory = baseRepositoryFactory;
     }
 
     private static class RepositoryNamer implements Namer<ArtifactRepository> {
@@ -152,10 +153,11 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     }
 
     private DependencyResolver addCustomDependencyResolver(Object userDescription, Closure configureClosure, Action<ArtifactRepository> orderAction) {
-        ArtifactRepository repository = resolverFactory.createRepository(userDescription);
-        DependencyResolver resolver = toResolver(DependencyResolver.class, repository);
+        ArtifactRepository repository = baseRepositoryFactory.createRepository(userDescription);
+        DependencyResolver resolver = baseRepositoryFactory.toResolver(repository);
         ConfigureUtil.configure(configureClosure, resolver);
-        addRepository(new FixedResolverArtifactRepository(resolver), "repository", orderAction);
+        ArtifactRepository resolverRepository = baseRepositoryFactory.createResolverBackedRepository(resolver);
+        addRepository(resolverRepository, "repository", orderAction);
         return resolver;
     }
 
@@ -167,20 +169,11 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     public List<DependencyResolver> getResolvers() {
         List<DependencyResolver> returnedResolvers = new ArrayList<DependencyResolver>();
         for (ArtifactRepository repository : this) {
-            returnedResolvers.add(((ArtifactRepositoryInternal) repository).createResolver());
+            returnedResolvers.add(baseRepositoryFactory.toResolver(repository));
         }
         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);
     }
@@ -208,29 +201,37 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     protected <T extends ArtifactRepository> T addRepository(T repository, String defaultName, Action<ArtifactRepository> action) {
         String repositoryName = repository.getName();
         if (!GUtil.isTrue(repositoryName)) {
-            repositoryName = findName(defaultName);
+            repositoryName = uniquifyName(defaultName);
             repository.setName(repositoryName);
         }
         assertCanAdd(repositoryName);
         action.execute(repository);
+        cast(ArtifactRepositoryInternal.class, repository).onAddToContainer(this);
+        return repository;
+    }
 
+    protected <T extends ArtifactRepository> T addRepository(T repository) {
+        return addRepository(repository, addLastAction);
+    }
+
+    protected <T extends ArtifactRepository> T addRepository(T repository, Action<? super T> insertion) {
+        repository.setName(uniquifyName(repository.getName()));
+        assertCanAdd(repository.getName());
+        insertion.execute(repository);
+        cast(ArtifactRepositoryInternal.class, repository).onAddToContainer(this);
         return repository;
     }
 
-    protected String findName(String defaultName) {
-        if (findByName(defaultName) == null) {
-            return defaultName;
+    private String uniquifyName(String proposedName) {
+        if (findByName(proposedName) == null) {
+            return proposedName;
         }
         for (int index = 2; true; index++) {
-            String candidate = String.format("%s%d", defaultName, index);
+            String candidate = String.format("%s%d", proposedName, 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/DefaultExcludeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
index 3c6fd6f..244f338 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
@@ -55,7 +55,7 @@ public class DefaultExcludeRule implements ExcludeRule {
     }
 
     public Map<String, String> getExcludeArgs() {
-        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.");
+        DeprecationLogger.nagUserOfDeprecated("The getExcludeArgs method", "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);
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
index 138ab8a..9c83315 100755
--- 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
@@ -15,10 +15,10 @@
  */
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.artifacts.ModuleVersionSelector;
 
-public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier, ModuleVersionSelector {
+public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier {
     private final String group;
     private final String name;
     private final String version;
@@ -74,4 +74,12 @@ public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier,
     public int hashCode() {
         return group.hashCode() ^ name.hashCode() ^ version.hashCode();
     }
+
+    public static ModuleVersionIdentifier newId(Module module) {
+        return new DefaultModuleVersionIdentifier(module.getGroup(), module.getName(), module.getVersion());
+    }
+
+    public static ModuleVersionIdentifier newId(String group, String name, String version) {
+        return new DefaultModuleVersionIdentifier(group, name, version);
+    }
 }
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
index e161e8a..b187453 100644
--- 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
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
 /**
@@ -55,6 +56,10 @@ public class DefaultModuleVersionSelector implements ModuleVersionSelector {
         return version;
     }
 
+    public boolean matchesStrictly(ModuleVersionIdentifier identifier) {
+        return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier);
+    }
+
     public DefaultModuleVersionSelector setVersion(String version) {
         this.version = version;
         return this;
@@ -96,4 +101,8 @@ public class DefaultModuleVersionSelector implements ModuleVersionSelector {
         result = 31 * result + (version != null ? version.hashCode() : 0);
         return result;
     }
-}
+
+    public static ModuleVersionSelector newSelector(String group, String name, String version) {
+        return new DefaultModuleVersionSelector(group, name, version);
+    }
+}
\ No newline at end of file
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
index 197cc84..e1c5b6b 100644
--- 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
@@ -32,5 +32,5 @@ public interface DependencyResolutionServices {
 
     Factory<ArtifactPublicationServices> getPublishServicesFactory();
 
-    ResolverFactory getResolverFactory();
+    BaseRepositoryFactory getBaseRepositoryFactory();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpec.java
new file mode 100644
index 0000000..91c395e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpec.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.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.specs.Spec;
+
+/**
+ * by Szczepan Faber, created at: 9/10/12
+ */
+public class ModuleVersionSelectorStrictSpec implements Spec<ModuleVersionIdentifier> {
+
+    private final ModuleVersionSelector selector;
+
+    public ModuleVersionSelectorStrictSpec(ModuleVersionSelector selector) {
+        assert selector != null;
+        this.selector = selector;
+    }
+
+    public boolean isSatisfiedBy(ModuleVersionIdentifier candidate) {
+        return candidate.getName().equals(selector.getName())
+                && candidate.getGroup().equals(selector.getGroup())
+                && candidate.getVersion().equals(selector.getVersion());
+    }
+}
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 46bb2dd..a6710a6 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,17 +16,15 @@
 
 package org.gradle.api.internal.artifacts;
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
 public class ResolvedConfigurationIdentifier {
-    private final String moduleGroup;
-    private final String moduleName;
-    private final String moduleVersion;
+    private final ModuleVersionIdentifier id;
     private final String configuration;
 
     public ResolvedConfigurationIdentifier(String moduleGroup, String moduleName, String moduleVersion,
                                            String configuration) {
-        this.moduleGroup = moduleGroup;
-        this.moduleName = moduleName;
-        this.moduleVersion = moduleVersion;
+        this.id = new DefaultModuleVersionIdentifier(moduleGroup, moduleName, moduleVersion);
         this.configuration = configuration;
     }
 
@@ -35,20 +33,24 @@ public class ResolvedConfigurationIdentifier {
     }
 
     public String getModuleGroup() {
-        return moduleGroup;
+        return id.getGroup();
     }
 
     public String getModuleName() {
-        return moduleName;
+        return id.getName();
     }
 
     public String getModuleVersion() {
-        return moduleVersion;
+        return id.getVersion();
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
     }
 
     @Override
     public String toString() {
-        return String.format("%s:%s:%s:%s", moduleGroup, moduleName, moduleVersion, configuration);
+        return String.format("%s:%s:%s:%s", getModuleGroup(), getModuleVersion(), getModuleName(), configuration);
     }
 
     @Override
@@ -62,13 +64,7 @@ public class ResolvedConfigurationIdentifier {
 
         ResolvedConfigurationIdentifier that = (ResolvedConfigurationIdentifier) o;
 
-        if (!moduleGroup.equals(that.moduleGroup)) {
-            return false;
-        }
-        if (!moduleName.equals(that.moduleName)) {
-            return false;
-        }
-        if (!moduleVersion.equals(that.moduleVersion)) {
+        if (!id.equals(that.id)) {
             return false;
         }
         if (!configuration.equals(that.configuration)) {
@@ -80,6 +76,6 @@ public class ResolvedConfigurationIdentifier {
 
     @Override
     public int hashCode() {
-        return moduleGroup.hashCode() ^ moduleName.hashCode() ^ configuration.hashCode();
+        return id.hashCode() ^ configuration.hashCode();
     }
 }
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
deleted file mode 100644
index 4ebd9e8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverFactory.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;
-
-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/ResolverResults.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
new file mode 100644
index 0000000..6811040
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverResults.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.artifacts.result.ResolutionResult;
+
+/**
+ * by Szczepan Faber, created at: 10/16/12
+ */
+public class ResolverResults {
+
+    private final ResolvedConfiguration resolvedConfiguration;
+    private final ResolutionResult resolutionResult;
+    private final ResolveException fatalFailure;
+
+    public ResolverResults(ResolvedConfiguration resolvedConfiguration, ResolveException fatalFailure) {
+        this(resolvedConfiguration, null, fatalFailure);
+    }
+
+    public ResolverResults(ResolvedConfiguration resolvedConfiguration, ResolutionResult resolutionResult) {
+        this(resolvedConfiguration, resolutionResult, null);
+    }
+
+    private ResolverResults(ResolvedConfiguration resolvedConfiguration, ResolutionResult resolutionResult, ResolveException fatalFailure) {
+        this.resolvedConfiguration = resolvedConfiguration;
+        this.resolutionResult = resolutionResult;
+        this.fatalFailure = fatalFailure;
+    }
+
+    //old model, slowly being replaced by the new model
+    public ResolvedConfiguration getResolvedConfiguration() {
+        return resolvedConfiguration;
+    }
+
+    //new model
+    public ResolutionResult getResolutionResult() {
+        if (fatalFailure != null) {
+            throw fatalFailure;
+        }
+        return resolutionResult;
+    }
+
+    public ResolverResults withResolvedConfiguration(ResolvedConfiguration resolvedConfiguration) {
+        return new ResolverResults(resolvedConfiguration, resolutionResult);
+    }
+}
\ No newline at end of file
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
index f67ccc6..6beda87 100644
--- 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
@@ -22,4 +22,4 @@ public interface ConfigurationInternal extends Configuration, DependencyMetaData
     DependencyResolutionListener getDependencyResolutionBroadcast();
 
     ResolutionStrategyInternal getResolutionStrategy();
-}
+}
\ No newline at end of file
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 b1ef068..c92b018 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
@@ -25,7 +25,7 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public class Configurations {
-    public static Set<String> getNames(Collection<Configuration> configurations, boolean includeExtended) {
+    public static Set<String> getNames(Collection<? extends Configuration> configurations, boolean includeExtended) {
         Set<Configuration> allConfigurations = new HashSet<Configuration>(configurations);
         if (includeExtended) {
             allConfigurations = createAllConfigurations(configurations);
@@ -41,7 +41,7 @@ public class Configurations {
         return getNames(configurations, false);
     }
 
-    private static Set<Configuration> createAllConfigurations(Collection<Configuration> configurations) {
+    private static Set<Configuration> createAllConfigurations(Collection<? extends Configuration> configurations) {
         Set<Configuration> allConfigurations = new HashSet<Configuration>();
         for (Configuration configuration : configurations) {
             allConfigurations.addAll(configuration.getHierarchy());
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 f3f6597..c2eb6be 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
@@ -19,13 +19,11 @@ package org.gradle.api.internal.artifacts.configurations;
 import groovy.lang.Closure;
 import org.gradle.api.*;
 import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.result.ResolutionResult;
 import org.gradle.api.file.FileCollection;
 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.DefaultPublishArtifactSet;
+import org.gradle.api.internal.artifacts.*;
 import org.gradle.api.internal.file.AbstractFileCollection;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
@@ -68,7 +66,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     // This lock only protects the following fields
     private final Object lock = new Object();
     private State state = State.UNRESOLVED;
-    private ResolvedConfiguration cachedResolvedConfiguration;
+    private ResolverResults cachedResolverResults;
     private final DefaultResolutionStrategy resolutionStrategy;
 
     public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
@@ -228,20 +226,24 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     public ResolvedConfiguration getResolvedConfiguration() {
+        resolveNow();
+        return cachedResolverResults.getResolvedConfiguration();
+    }
+
+    private void resolveNow() {
         synchronized (lock) {
             if (state == State.UNRESOLVED) {
                 DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
                 ResolvableDependencies incoming = getIncoming();
                 broadcast.beforeResolve(incoming);
-                cachedResolvedConfiguration = dependencyResolver.resolve(this);
-                if (cachedResolvedConfiguration.hasError()) {
+                cachedResolverResults = dependencyResolver.resolve(this);
+                if (cachedResolverResults.getResolvedConfiguration().hasError()) {
                     state = State.RESOLVED_WITH_FAILURES;
                 } else {
                     state = State.RESOLVED;
                 }
                 broadcast.afterResolve(incoming);
             }
-            return cachedResolvedConfiguration;
         }
     }
 
@@ -556,5 +558,11 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         public void afterResolve(Closure action) {
             resolutionListenerBroadcast.add("afterResolve", action);
         }
+
+        public ResolutionResult getResolutionResult() {
+            //TODO SF unit test
+            DefaultConfiguration.this.resolveNow();
+            return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
+        }
     }
-}
+}
\ 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
index 9e6121f..b65975b 100644
--- 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.configurations.dynamicversion;
 
+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;
@@ -25,7 +26,7 @@ import java.io.File;
 public interface CachePolicy {
     boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis);
 
-    boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis);
+    boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, ModuleRevisionId moduleRevisionId, long ageMillis);
 
     boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, 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
index 6cbe5fa..68b3509 100644
--- 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
@@ -15,16 +15,13 @@
  */
 package org.gradle.api.internal.artifacts.configurations.dynamicversion;
 
+import org.apache.ivy.core.module.id.ModuleRevisionId;
 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 org.gradle.api.artifacts.cache.*;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -73,7 +70,7 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
             }
         });
     }
-    
+
     private void cacheMissingModulesAndArtifactsFor(final int value, final TimeUnit units) {
         eachModule(new Action<ModuleResolutionControl>() {
             public void execute(ModuleResolutionControl moduleResolutionControl) {
@@ -100,11 +97,11 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
                 return dependencyResolutionControl.mustCheck();
             }
         }
-        
+
         return false;
     }
 
-    public boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, final long ageMillis) {
+    public boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, ModuleRevisionId moduleRevisionId, final long ageMillis) {
         return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, false);
     }
 
@@ -175,27 +172,27 @@ public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
         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;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java
index 7bc6ef6..1d60c37 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java
@@ -16,6 +16,8 @@
 package org.gradle.api.internal.artifacts.dependencies;
 
 import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionSelectorStrictSpec;
 
 public abstract class AbstractExternalDependency extends AbstractModuleDependency implements ExternalDependency {
     public AbstractExternalDependency(String configuration) {
@@ -33,4 +35,8 @@ public abstract class AbstractExternalDependency extends AbstractModuleDependenc
         }
         return isForce() == dependencyRhs.isForce();
     }
+
+    public boolean matchesStrictly(ModuleVersionIdentifier identifier) {
+        return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.java
new file mode 100644
index 0000000..9be55b4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactory.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.artifacts.dsl;
+
+import org.gradle.api.Action;
+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;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME;
+import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME;
+import static org.gradle.util.CollectionUtils.flattenToList;
+
+public class DefaultRepositoryFactory implements RepositoryFactoryInternal {
+
+    public static final String FLAT_DIR_DEFAULT_NAME = "flatDir";
+    private static final String MAVEN_REPO_DEFAULT_NAME = "maven";
+    private static final String IVY_REPO_DEFAULT_NAME = "ivy";
+
+    private final BaseRepositoryFactory baseRepositoryFactory;
+
+    public DefaultRepositoryFactory(BaseRepositoryFactory baseRepositoryFactory) {
+        this.baseRepositoryFactory = baseRepositoryFactory;
+    }
+
+    public BaseRepositoryFactory getBaseRepositoryFactory() {
+        return baseRepositoryFactory;
+    }
+
+    public FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action) {
+        return configure(getBaseRepositoryFactory().createFlatDirRepository(), action, FLAT_DIR_DEFAULT_NAME);
+    }
+
+    public FlatDirectoryArtifactRepository flatDir(Map<String, ?> args) {
+        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
+        if (modifiedArgs.containsKey("dirs")) {
+            modifiedArgs.put("dirs", flattenToList(modifiedArgs.get("dirs")));
+        }
+
+        return configure(getBaseRepositoryFactory().createFlatDirRepository(), modifiedArgs, FLAT_DIR_DEFAULT_NAME);
+    }
+
+    public MavenArtifactRepository mavenLocal() {
+        return configure(baseRepositoryFactory.createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME);
+    }
+
+    public MavenArtifactRepository mavenCentral() {
+        return configure(baseRepositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME);
+    }
+
+    public MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) {
+        return configure(baseRepositoryFactory.createMavenRepository(), action, MAVEN_REPO_DEFAULT_NAME);
+    }
+
+    public IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action) {
+        return configure(baseRepositoryFactory.createIvyRepository(), action, IVY_REPO_DEFAULT_NAME);
+    }
+
+    private <T extends ArtifactRepository> T configure(T repository, Action<? super T> action, String name) {
+        action.execute(repository);
+        return configure(repository, name);
+    }
+
+    private <T extends ArtifactRepository> T configure(T repository, Map<String, ?> properties, String name) {
+        ConfigureUtil.configureByMap(properties, repository);
+        return configure(repository, name);
+    }
+
+    private <T extends ArtifactRepository> T configure(T repository, String name) {
+        String repositoryName = repository.getName();
+        if (!GUtil.isTrue(repositoryName)) {
+            repository.setName(name);
+        }
+        return repository;
+    }
+
+}
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 16147df..387f5fc 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,7 +15,6 @@
  */
 package org.gradle.api.internal.artifacts.dsl;
 
-import com.google.common.collect.Lists;
 import groovy.lang.Closure;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.Action;
@@ -23,60 +22,67 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.internal.reflect.Instantiator;
+import org.gradle.api.internal.Actions;
+import org.gradle.api.internal.ClosureBackedAction;
 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.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.gradle.util.CollectionUtils.flattenToList;
+
 /**
  * @author Hans Dockter
  */
 public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler, ResolverProvider {
-    public DefaultRepositoryHandler(ResolverFactory resolverFactory, Instantiator instantiator) {
-        super(resolverFactory, instantiator);
+
+    private final RepositoryFactoryInternal repositoryFactory;
+
+    public DefaultRepositoryHandler(RepositoryFactoryInternal repositoryFactory, Instantiator instantiator) {
+        super(repositoryFactory.getBaseRepositoryFactory(), instantiator);
+        this.repositoryFactory = repositoryFactory;
     }
 
     public FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action) {
-        return addRepository(getResolverFactory().createFlatDirRepository(), action, "flatDir");
+        return addRepository(repositoryFactory.flatDir(action));
     }
 
     public FlatDirectoryArtifactRepository flatDir(Closure configureClosure) {
-        return addRepository(getResolverFactory().createFlatDirRepository(), configureClosure, "flatDir");
+        return flatDir(new ClosureBackedAction<FlatDirectoryArtifactRepository>(configureClosure));
     }
 
     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 addRepository(getResolverFactory().createFlatDirRepository(), modifiedArgs, "flatDir");
+        return addRepository(repositoryFactory.flatDir(args));
     }
 
     public MavenArtifactRepository mavenCentral() {
-        return mavenCentral(Collections.<String, Object>emptyMap());
+        return addRepository(repositoryFactory.mavenCentral());
     }
 
     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"));
+            DeprecationLogger.nagUserOfDeprecated(
+                    "The 'urls' property of the RepositoryHandler.mavenCentral() method",
+                    "You should use the 'artifactUrls' property to define additional artifact locations"
+            );
+            List<?> urls = flattenToList(modifiedArgs.remove("urls"));
             modifiedArgs.put("artifactUrls", urls);
         }
-        return addRepository(getResolverFactory().createMavenCentralRepository(), modifiedArgs, DEFAULT_MAVEN_CENTRAL_REPO_NAME);
+
+        MavenArtifactRepository repo = repositoryFactory.mavenCentral();
+        ConfigureUtil.configureByMap(modifiedArgs, repo);
+        return addRepository(repo);
     }
 
     public MavenArtifactRepository mavenLocal() {
-        return addRepository(getResolverFactory().createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME);
+        return addRepository(repositoryFactory.mavenLocal());
     }
 
     public DependencyResolver mavenRepo(Map<String, ?> args) {
@@ -86,48 +92,41 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
     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"));
+            List<?> urls = flattenToList(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.");
+                DeprecationLogger.nagUserOfDeprecated(
+                        "The 'urls' property of the RepositoryHandler.mavenRepo() method",
+                        "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());
+                List<?> extraUrls = urls.subList(1, urls.size());
                 modifiedArgs.put("artifactUrls", extraUrls);
             }
         }
 
-        MavenArtifactRepository repository = getResolverFactory().createMavenRepository();
+        MavenArtifactRepository repository = repositoryFactory.maven(Actions.doNothing());
         ConfigureUtil.configureByMap(modifiedArgs, repository);
-        DependencyResolver resolver = toResolver(DependencyResolver.class, repository);
+        DependencyResolver resolver = repositoryFactory.getBaseRepositoryFactory().toResolver(repository);
         ConfigureUtil.configure(configClosure, resolver);
-        addRepository(new FixedResolverArtifactRepository(resolver), "maven");
+        addRepository(new FixedResolverArtifactRepository(resolver));
         return resolver;
     }
 
-    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 MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) {
-        return addRepository(getResolverFactory().createMavenRepository(), action, "maven");
+        return addRepository(repositoryFactory.maven(action));
     }
 
     public MavenArtifactRepository maven(Closure closure) {
-        return addRepository(getResolverFactory().createMavenRepository(), closure, "maven");
+        return maven(new ClosureBackedAction<MavenArtifactRepository>(closure));
     }
 
     public IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action) {
-        return addRepository(getResolverFactory().createIvyRepository(), action, "ivy");
+        return addRepository(repositoryFactory.ivy(action));
     }
 
     public IvyArtifactRepository ivy(Closure closure) {
-        return addRepository(getResolverFactory().createIvyRepository(), closure, "ivy");
+        return ivy(new ClosureBackedAction<IvyArtifactRepository>(closure));
     }
 
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java
new file mode 100644
index 0000000..bc09926
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactory.java
@@ -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.api.internal.artifacts.dsl;
+
+import org.gradle.api.Action;
+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;
+
+/**
+ * A repository factory is capable of creating different kinds of {@link org.gradle.api.artifacts.repositories.ArtifactRepository} types.
+ */
+public interface RepositoryFactory {
+
+    /*
+        Internal note: Not everything was copied from RepositoryHandler, only what wasn't going to be deprecated eventually.
+     */
+
+    /**
+     * Creates 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 repository you might specify your
+     * dependencies like <code>":junit:4.4"</code> instead of <code>"junit:junit:4.4"</code>.
+     *
+     * 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 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. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}</td></tr>
+     * </table>
+     *
+     * <p>Examples:
+     * <pre>
+     * repositories {
+     *     flatDir name: 'libs', dirs: "$projectDir/libs"
+     *     flatDir dirs: ["$projectDir/libs1", "$projectDir/libs2"]
+     * }
+     * </pre>
+     * </p>
+     *
+     * @param args The arguments used to configure the repository.
+     * @return the created repository
+     * @throws org.gradle.api.InvalidUserDataException In the case neither rootDir nor rootDirs is specified of if both
+     * are specified.
+     */
+    FlatDirectoryArtifactRepository flatDir(Map<String, ?> args);
+
+    /**
+     * Creates and 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);
+
+    /**
+     * Creates a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
+     * {@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}.
+     *
+     * @return the created repository
+     */
+    MavenArtifactRepository mavenCentral();
+
+    /**
+     * Creates a repository which looks in the local Maven cache for dependencies. The name of the repository is
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
+     *
+     * @return the created repository
+     */
+    MavenArtifactRepository mavenLocal();
+
+    /**
+     * Creates and configures a Maven repository.
+     *
+     * @param action The action to use to configure the repository.
+     * @return The created repository.
+     */
+    MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action);
+
+    /**
+     * Creates and configures an Ivy repository.
+     *
+     * @param action The action to use to configure the repository.
+     * @return The created repository.
+     */
+    IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.java
new file mode 100644
index 0000000..5667d8b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/RepositoryFactoryInternal.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.dsl;
+
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
+
+public interface RepositoryFactoryInternal extends RepositoryFactory {
+
+    BaseRepositoryFactory getBaseRepositoryFactory();
+
+}
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 592e4bf..4ea3563 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,7 +31,6 @@ 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/repositories/AbstractArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java
new file mode 100644
index 0000000..f87eb00
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.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.api.internal.artifacts.repositories;
+
+import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.util.DeprecationLogger;
+
+public abstract class AbstractArtifactRepository implements ArtifactRepositoryInternal {
+
+    private String name;
+    private boolean isPartOfContainer;
+
+    public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) {
+        isPartOfContainer = true;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (isPartOfContainer) {
+            DeprecationLogger.nagUserOfDeprecated("Changing the name of an ArtifactRepository that is part of a container", "Set the name when creating the repository");
+        }
+        this.name = name;
+    }
+}
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 e07b625..516195e 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,8 +16,13 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.NamedDomainObjectCollection;
 import org.gradle.api.artifacts.repositories.ArtifactRepository;
 
 public interface ArtifactRepositoryInternal extends ArtifactRepository {
+
     DependencyResolver createResolver();
+
+    void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container);
+
 }
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
index b2fe5a8..a89836e 100644
--- 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
@@ -18,7 +18,7 @@ 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 {
+public class FixedResolverArtifactRepository extends AbstractArtifactRepository implements ArtifactRepository, ArtifactRepositoryInternal {
     protected final DependencyResolver resolver;
 
     public FixedResolverArtifactRepository(DependencyResolver resolver) {
@@ -31,9 +31,14 @@ public class FixedResolverArtifactRepository implements ArtifactRepository, Arti
 
     public void setName(String name) {
         resolver.setName(name);
+
+        // We are doing this because we are relying on the deprecation warning that
+        // AbstractArtifactRepository (super) issues. This is a bit awkward.
+        super.setName(name);
     }
 
     public DependencyResolver createResolver() {
         return resolver;
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.java
new file mode 100644
index 0000000..e788d44
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/result/ResolvedDependencyResultPrinter.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.result;
+
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+/**
+ * Created: 17/08/2012
+ *
+ * @author Szczepan Faber
+ */
+public class ResolvedDependencyResultPrinter {
+
+    public static String print(ResolvedDependencyResult result) {
+        if (!result.getRequested().matchesStrictly(result.getSelected().getId())) {
+            return requested(result) + " -> " + result.getSelected().getId().getVersion();
+        } else {
+            return requested(result);
+        }
+    }
+
+    private static String requested(ResolvedDependencyResult result) {
+        return result.getRequested().getGroup() + ":" + result.getRequested().getName() + ":" + result.getRequested().getVersion();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparator.java
new file mode 100644
index 0000000..2ea8205
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparator.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.version;
+
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
+
+import java.util.Comparator;
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+public class LatestVersionSemanticComparator implements Comparator<String> {
+
+    public int compare(String left, String right) {
+        return new LatestRevisionStrategy().getComparator().compare(new SimpleArtifactInfo(left), new SimpleArtifactInfo(right));
+    }
+
+    private static class SimpleArtifactInfo implements ArtifactInfo {
+
+        private final String version;
+
+        public SimpleArtifactInfo(String version) {
+            this.version = version;
+        }
+
+        public String getRevision() {
+            return version;
+        }
+
+        public long getLastModified() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
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
index b8dd642..3c121c8 100644
--- 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
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.classpath;
 
 import org.gradle.api.UncheckedIOException;
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,7 +40,7 @@ public class ManifestUtil {
             paths.add(path);
         }
 
-        return GUtil.join(paths, " ");
+        return CollectionUtils.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.
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
index 472d9d8..1b7cf03 100644
--- 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
@@ -15,10 +15,10 @@
  */
 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.internal.Actions;
 import org.gradle.api.specs.Specs;
+import org.gradle.listener.ActionBroadcast;
 
 public class CollectionEventRegister<T> {
 
@@ -55,22 +55,21 @@ public class CollectionEventRegister<T> {
     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;
+        private final CollectionFilter<? super S> filter;
 
-        public FilteringCollectionEventRegister(CollectionFilter<S> filter, ActionBroadcast<S> addActions, ActionBroadcast<S> removeActions) {
+        public FilteringCollectionEventRegister(CollectionFilter<? super 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));
+            return super.registerAddAction(Actions.<S>filter(addAction, filter));
         }
 
         public Action<? super S> registerRemoveAction(Action<? super S> removeAction) {
-            return super.registerRemoveAction(new FilteredAction<S>(filter, removeAction));
+            return super.registerRemoveAction(Actions.<S>filter(removeAction, filter));
         }
 
         public <K extends S> CollectionEventRegister<K> filtered(CollectionFilter<K> filter) {
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 14483df..e2ac907 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
@@ -61,7 +61,7 @@ public abstract class AbstractFileCollection implements FileCollection, MinimalF
     }
 
     public String getAsPath() {
-        return GUtil.join(getFiles(), File.pathSeparator);
+        return GUtil.asPath(getFiles());
     }
 
     public boolean contains(File file) {
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 72d3c01..75582dd 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
@@ -26,7 +26,7 @@ import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -86,7 +86,7 @@ public abstract class AbstractFileResolver implements FileResolver {
                 }
             }
 
-            String resolvedPath = GUtil.join(path, File.separator);
+            String resolvedPath = CollectionUtils.join(File.separator, path);
             boolean needLeadingSeparator = File.listRoots()[0].getPath().startsWith(File.separator);
             if (needLeadingSeparator) {
                 resolvedPath = File.separator + resolvedPath;
@@ -108,7 +108,7 @@ public abstract class AbstractFileResolver implements FileResolver {
             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));
+                    current = new File(current, CollectionUtils.join(File.separator, path.subList(pos, path.size())));
                     break;
                 }
                 current = child;
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 6f658e4..fb2b901 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,12 +19,13 @@ 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.Chmod;
 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
@@ -62,13 +63,17 @@ public abstract class  AbstractFileTreeElement implements FileTreeElement {
             } else {
                 copyFile(target);
             }
-            FileSystems.getDefault().chmod(target, getMode());
+            getChmod().chmod(target, getMode());
             return true;
         } catch (Exception e) {
             throw new GradleException(String.format("Could not copy %s to '%s'.", getDisplayName(), target), e);
         }
     }
 
+    protected Chmod getChmod() {
+        return FileSystems.getDefault();
+    }
+
     private void validateTimeStamps() {
         final long lastModified = getLastModified();
         if(lastModified < 0) {
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
index 884b171..49c4394 100644
--- 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
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file;
 
 import org.apache.commons.lang.StringUtils;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GUtil;
 
 import java.io.File;
@@ -58,7 +59,7 @@ public class BaseDirFileResolver extends AbstractFileResolver {
         if (targetPath.isEmpty()) {
             return ".";
         }
-        return GUtil.join(targetPath, File.separator);
+        return CollectionUtils.join(File.separator, targetPath);
     }
 
     @Override
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
index 1872cea..71b343d 100755
--- 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
@@ -19,8 +19,8 @@ package org.gradle.api.internal.file;
 import org.gradle.api.Nullable;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.internal.Factory;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,21 +33,21 @@ public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
     }
 
     public File newTemporaryFile(String... path) {
-        return GFileUtils.canonicalise(new File(baseDirFactory.create(), GUtil.join(path, "/")));
+        return GFileUtils.canonicalise(new File(baseDirFactory.create(), CollectionUtils.join("/", path)));
     }
 
     public File createTemporaryFile(String prefix, @Nullable String suffix, String... path) {
-        File dir = new File(baseDirFactory.create(), GUtil.join(path, "/"));
+        File dir = new File(baseDirFactory.create(), CollectionUtils.join("/", path));
         GFileUtils.createDirectory(dir);
         try {
             return File.createTempFile(prefix, suffix, dir);
         } catch (IOException e) {
-            throw new UncheckedIOException(e.getMessage(), e);
+            throw new UncheckedIOException(e);
         }
     }
 
     public File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
-        File dir = new File(baseDirFactory.create(), GUtil.join(path, "/"));
+        File dir = new File(baseDirFactory.create(), CollectionUtils.join("/", path));
         GFileUtils.createDirectory(dir);
         try {
             // TODO: This is not a great paradigm for creating a temporary directory.
@@ -57,7 +57,7 @@ public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
             tmpDir.mkdir();
             return tmpDir;
         } catch (IOException e) {
-            throw new UncheckedIOException(e.getMessage(), e);
+            throw new UncheckedIOException(e);
         }
     }
 }
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
index c30638e..2b9d3c8 100644
--- 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
@@ -90,8 +90,10 @@ public class FileOrUriNotationParser<T extends Serializable> implements Notation
                 }
             }
         } 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()));
+            DeprecationLogger.nagUserOfDeprecated(
+                    String.format("Converting class %s to File using toString() method", notation.getClass().getName()),
+                    "Please use java.io.File, java.lang.String, java.net.URL, or java.net.URI instead"
+            );
         }
         return (T) new File(notation.toString());
     }
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
index 5d3bd68..3c1fb5b 100644
--- 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
@@ -16,18 +16,13 @@
 
 package org.gradle.api.internal.file;
 
-import org.gradle.api.Nullable;
 import org.gradle.internal.Factory;
 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 Factory<File>() {
             public File create() {
@@ -35,23 +30,4 @@ public class TmpDirTemporaryFileProvider extends DefaultTemporaryFileProvider {
             }
         });
     }
-
-    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/TarFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
index 648ea73..ed016c5 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
@@ -62,8 +62,9 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             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()));
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(
+                    String.format("The specified tar file %s does not exist and will be silently ignored", getDisplayName())
+            );
             return;
         } catch (ResourceException e) {
             throw new InvalidUserDataException(String.format("Cannot expand %s.", getDisplayName()), 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 7c3d1e5..d229bf4 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
@@ -60,8 +60,9 @@ 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()));
+            DeprecationLogger.nagUserOfDeprecatedBehaviour(
+                    String.format("The specified zip file %s does not exist and will be silently ignored", getDisplayName())
+            );
             return;
         }
         if (!zipFile.isFile()) {
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
index 3fd1fde..43fa312 100644
--- 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
@@ -54,10 +54,10 @@ public class PathNotationParser<T extends String> implements NotationParser<T> {
                 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()));
+        DeprecationLogger.nagUserOfDeprecated(
+                String.format("Converting class %s to path using toString() method", notation.getClass().getName()),
+                "Please use java.io.File, java.lang.CharSequence, java.lang.Number, java.util.concurrent.Callable or groovy.lang.Closure"
+        );
         return (T) notation.toString();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/AbstractFileStoreEntry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/AbstractFileStoreEntry.java
new file mode 100644
index 0000000..00bf480
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/AbstractFileStoreEntry.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.filestore;
+
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+public abstract class AbstractFileStoreEntry implements FileStoreEntry {
+
+    public HashValue getSha1() {
+        return HashUtil.createHash(getFile(), "SHA1");
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java
new file mode 100644
index 0000000..bdf2339
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStore.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.filestore;
+
+import org.gradle.api.Action;
+
+import java.io.File;
+
+public interface FileStore<K> {
+
+    FileStoreEntry move(K key, File source);
+
+    FileStoreEntry copy(K key, File source);
+
+    void moveFilestore(File destination);
+
+    FileStoreEntry add(K key, Action<File> addAction);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
similarity index 100%
rename from subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
rename to subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
similarity index 100%
rename from subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
rename to subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java
new file mode 100644
index 0000000..8127a95
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.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.filestore;
+
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.util.hash.HashUtil;
+
+import java.io.File;
+import java.util.Set;
+
+public class GroupedAndNamedUniqueFileStore<K> implements FileStore<K>, FileStoreSearcher<K> {
+
+    private PathKeyFileStore delegate;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private final Transformer<String, K> grouper;
+    private final Transformer<String, K> namer;
+
+
+    public GroupedAndNamedUniqueFileStore(PathKeyFileStore delegate, TemporaryFileProvider temporaryFileProvider, Transformer<String, K> grouper, Transformer<String, K> namer) {
+        this.delegate = delegate;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.grouper = grouper;
+        this.namer = namer;
+    }
+
+    public FileStoreEntry move(K key, File source) {
+        return delegate.move(toPath(key, getChecksum(source)), source);
+    }
+
+    public FileStoreEntry copy(K key, File source) {
+        return delegate.copy(toPath(key, getChecksum(source)), source);
+    }
+
+    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 temporaryFileProvider.createTemporaryFile("filestore", "bin");
+    }
+
+    public void moveFilestore(File destination) {
+        delegate.moveFilestore(destination);
+    }
+
+    public FileStoreEntry add(K key, Action<File> addAction) {
+        //We cannot just delegate to the add method as we need the file content for checksum calculation here
+        //and reexecuting the action isn't acceptable
+        final File tempFile = getTempFile();
+        addAction.execute(tempFile);
+        final String groupedAndNamedKey = toPath(key, getChecksum(tempFile));
+        return delegate.move(groupedAndNamedKey, tempFile);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java
new file mode 100644
index 0000000..6b22744
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathKeyFileStore.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.EmptyFileVisitor;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.internal.file.copy.DeleteActionImpl;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * File store that accepts the target path as the key for the entry.
+ *
+ * This implementation is explicitly NOT THREAD SAFE. Concurrent access must be organised externally.
+ * <p>
+ * There is always at most one entry for a given key for this file store. If an entry already exists at the given path, it will be overwritten.
+ * Paths can contain directory components, which will be created on demand.
+ * <p>
+ * This file store is self repairing in so far that any files partially written before a fatal error will be ignored and
+ * removed at a later time.
+ * <p>
+ * This file store also provides searching via relative ant path patterns.
+ */
+public class PathKeyFileStore implements FileStore<String>, FileStoreSearcher<String> {
+
+    /*
+        When writing a file into the filestore a marker file with this suffix is written alongside,
+        then removed after the write. This is used to detect partially written files (due to a serious crash)
+        and to silently clean them.
+     */
+    public static final String IN_PROGRESS_MARKER_FILE_SUFFIX = ".fslck";
+
+    private File baseDir;
+    private final DeleteActionImpl deleteAction = new DeleteActionImpl(new IdentityFileResolver());
+
+    public PathKeyFileStore(File baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    protected File getBaseDir() {
+        return baseDir;
+    }
+
+    public FileStoreEntry move(String path, File source) {
+        return saveIntoFileStore(source, getFile(path), true);
+    }
+
+    public FileStoreEntry copy(String path, File source) {
+        return saveIntoFileStore(source, getFile(path), false);
+    }
+
+    private File getFile(String path) {
+        return new File(baseDir, path);
+    }
+
+    private File getFileWhileCleaningInProgress(String path) {
+        File file = getFile(path);
+        File markerFile = getInProgressMarkerFile(file);
+        if (markerFile.exists()) {
+            deleteAction.delete(file);
+            deleteAction.delete(markerFile);
+        }
+        return file;
+    }
+
+    public void moveFilestore(File destination) {
+        if (baseDir.exists()) {
+            GFileUtils.moveDirectory(baseDir, destination);
+        }
+        baseDir = destination;
+    }
+
+    public FileStoreEntry add(String path, Action<File> addAction) {
+        String error = String.format("Failed to add into filestore '%s' at '%s' ", getBaseDir().getAbsolutePath(), path);
+        return doAdd(getFile(path), error, addAction);
+    }
+
+    protected FileStoreEntry saveIntoFileStore(final File source, final File destination, final boolean isMove) {
+        String verb = isMove ? "move" : "copy";
+
+        if (!source.exists()) {
+            throw new GradleException(String.format("Cannot %s '%s' into filestore @ '%s' as it does not exist", verb, source, destination));
+        }
+
+        String error = String.format("Failed to %s file '%s' into filestore at '%s' ", verb, source, destination);
+
+        return doAdd(destination, error, new Action<File>() {
+            public void execute(File file) {
+                if (isMove) {
+                    GFileUtils.moveFile(source, destination);
+                } else {
+                    GFileUtils.copyFile(source, destination);
+                }
+            }
+        });
+    }
+
+    protected FileStoreEntry doAdd(File destination, String failureDescription, Action<File> action) {
+        try {
+            GFileUtils.parentMkdirs(destination);
+            File inProgressMarkerFile = getInProgressMarkerFile(destination);
+            GFileUtils.touch(inProgressMarkerFile);
+            try {
+                deleteAction.delete(destination);
+                action.execute(destination);
+            } catch (Throwable t) {
+                deleteAction.delete(destination);
+                throw t;
+            } finally {
+                deleteAction.delete(inProgressMarkerFile);
+            }
+        } catch (Throwable t) {
+            throw new GradleException(failureDescription, t);
+        }
+        return entryAt(destination);
+    }
+
+    public Set<? extends FileStoreEntry> search(String pattern) {
+        if (!getBaseDir().exists()) {
+            return Collections.emptySet();
+        }
+
+        final Set<FileStoreEntry> entries = new HashSet<FileStoreEntry>();
+        findFiles(pattern).visit(new EmptyFileVisitor() {
+            public void visitFile(FileVisitDetails fileDetails) {
+                final File file = fileDetails.getFile();
+                // We cannot clean in progress markers, or in progress files here because
+                // the file system visitor stuff can't handle the file system mutating while visiting
+                if (!isInProgressMarkerFile(file) && !isInProgressFile(file)) {
+                    entries.add(entryAt(file));
+                }
+            }
+        });
+
+        return entries;
+    }
+
+    private File getInProgressMarkerFile(File file) {
+        return new File(file.getParent(), file.getName() + IN_PROGRESS_MARKER_FILE_SUFFIX);
+    }
+
+    private boolean isInProgressMarkerFile(File file) {
+        return file.getName().endsWith(IN_PROGRESS_MARKER_FILE_SUFFIX);
+    }
+
+    private boolean isInProgressFile(File file) {
+        return getInProgressMarkerFile(file).exists();
+    }
+
+    private DirectoryFileTree findFiles(String pattern) {
+        DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
+        PatternFilterable patternSet = new PatternSet();
+        patternSet.include(pattern);
+        return fileTree.filter(patternSet);
+    }
+
+    protected FileStoreEntry entryAt(File file) {
+        return entryAt(GFileUtils.relativePath(baseDir, file));
+    }
+
+    protected FileStoreEntry entryAt(final String path) {
+        return new AbstractFileStoreEntry() {
+            public File getFile() {
+                return new File(baseDir, path);
+            }
+        };
+    }
+
+    public FileStoreEntry get(String key) {
+        final File file = getFileWhileCleaningInProgress(key);
+        if (file.exists()) {
+            return entryAt(file);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStore.java
new file mode 100644
index 0000000..ab03a5a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStore.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.filestore;
+
+import org.gradle.api.Action;
+
+import java.io.File;
+import java.util.Set;
+
+public class PathNormalisingKeyFileStore implements FileStore<String>, FileStoreSearcher<String> {
+
+    private final PathKeyFileStore delegate;
+
+    public PathNormalisingKeyFileStore(File baseDir) {
+        this(new PathKeyFileStore(baseDir));
+    }
+
+    public PathNormalisingKeyFileStore(PathKeyFileStore delegate) {
+        this.delegate = delegate;
+    }
+
+    public FileStoreEntry move(String key, File source) {
+        return delegate.move(normalizePath(key), source);
+    }
+
+    public FileStoreEntry copy(String key, File source) {
+        return delegate.copy(key, source);
+    }
+
+    protected String normalizePath(String path) {
+        return path.replaceAll("[^\\d\\w\\./]", "_");
+    }
+
+    protected String normalizeSearchPath(String path) {
+        return path.replaceAll("[^\\d\\w\\.\\*/]", "_");
+    }
+
+    public void moveFilestore(File destination) {
+        delegate.moveFilestore(destination);
+    }
+
+    public FileStoreEntry add(String key, Action<File> addAction) {
+        return delegate.add(normalizePath(key), addAction);
+    }
+
+    public Set<? extends FileStoreEntry> search(String key) {
+        return delegate.search(normalizeSearchPath(key));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStore.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStore.java
new file mode 100644
index 0000000..21c759c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStore.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.api.internal.filestore;
+
+import org.gradle.api.Action;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+
+/**
+ * Assumes that files do not need to be replaced in the filestore.
+ *
+ * Can be used as an optimisation if path contains a checksum of the file, as there is no point to perform the replace in that circumstance.
+ */
+public class UniquePathKeyFileStore extends PathKeyFileStore {
+
+    public UniquePathKeyFileStore(File baseDir) {
+        super(baseDir);
+    }
+
+    @Override
+    public FileStoreEntry move(String path, File source) {
+        FileStoreEntry entry = super.move(path, source);
+        if (source.exists()) {
+            GFileUtils.deleteQuietly(source);
+        }
+        return entry;
+    }
+
+    @Override
+    protected FileStoreEntry doAdd(File destination, String failureDescription, Action<File> action) {
+        if (destination.exists()) {
+            return entryAt(destination);
+        }
+        return super.doAdd(destination, failureDescription, action);
+    }
+}
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
index f0f6499..94906af 100644
--- 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
@@ -32,11 +32,15 @@ import java.util.Set;
  * by Szczepan Faber, created at: 11/8/11
  */
 public class NotationParserBuilder<T> {
-    private Class<T> resultingType;
+    private TypeInfo<T> resultingType;
     private String invalidNotationMessage;
     private Collection<NotationParser<? extends T>> notationParsers = new LinkedList<NotationParser<? extends T>>();
 
     public NotationParserBuilder<T> resultingType(Class<T> resultingType) {
+        return resultingType(new TypeInfo<T>(resultingType));
+    }
+
+    public NotationParserBuilder<T> resultingType(TypeInfo<T> resultingType) {
         this.resultingType = resultingType;
         return this;
     }
@@ -65,14 +69,14 @@ public class NotationParserBuilder<T> {
     }
 
     private <S> NotationParser<S> wrapInErrorHandling(NotationParser<S> parser) {
-        return new ErrorHandlingNotationParser<S>(resultingType.getSimpleName(), invalidNotationMessage, parser);
+        return new ErrorHandlingNotationParser<S>(resultingType.getTargetType().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.add(new JustReturningParser<T>(resultingType.getTargetType()));
         composites.addAll(this.notationParsers);
 
         return new CompositeNotationParser<T>(composites);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.java
new file mode 100644
index 0000000..89993b5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/TypeInfo.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.notations;
+
+/**
+ * Type literal, useful for nested Generics.
+ *
+ * by Szczepan Faber, created at: 10/12/12
+ */
+public class TypeInfo<T> {
+    private final Class<T> targetType;
+
+    public TypeInfo(Class targetType) {
+        assert targetType != null;
+        this.targetType = targetType;
+    }
+
+    Class<T> getTargetType() {
+        return targetType;
+    }
+}
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
index 32bfe0d..e93ab7a 100644
--- 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
@@ -22,7 +22,7 @@ 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;
+
+    void describe(Collection<String> candidateFormats);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParser.java
new file mode 100644
index 0000000..6f9cefd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParser.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.api.internal.notations.parsers;
+
+import groovy.lang.Closure;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 10/12/12
+ */
+public class ClosureToSpecNotationParser<T> implements NotationParser<Spec<T>> {
+    public Spec<T> parseNotation(Object notation) throws UnsupportedNotationException {
+        if (notation instanceof Closure) {
+            return Specs.convertClosureToSpec((Closure) notation);
+        }
+        throw new UnsupportedNotationException(notation);
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Closure that returns boolean. See the dsl reference for information what parameters are passed into the closure.");
+    }
+}
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
index edeeea9..b64bf1b 100644
--- 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
@@ -215,6 +215,14 @@ public class DefaultConvention implements Convention {
             throw new MissingMethodException(name, Convention.class, args);
         }
 
+        public boolean isMayImplementMissingMethods() {
+            return false;
+        }
+
+        public boolean isMayImplementMissingProperties() {
+            return false;
+        }
+
         public Object methodMissing(String name, Object args) {
             return invokeMethod(name, (Object[])args);
         }
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
index 1d2c3c0..8ac01a4 100644
--- 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
@@ -53,7 +53,7 @@ public class DefaultExtraPropertiesExtension extends GroovyObjectSupport impleme
         try {
             return get(name);
         } catch (UnknownPropertyException e) {
-            throw new MissingPropertyException(e.getMessage());
+            throw new MissingPropertyException(e.getMessage(), name, null);
         }
     }
 
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 b8bdcc3..fc0c279 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
@@ -20,6 +20,8 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Plugin;
 import org.gradle.api.plugins.PluginInstantiationException;
 import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.util.GUtil;
 
 import java.net.URL;
@@ -35,38 +37,33 @@ public class DefaultPluginRegistry implements PluginRegistry {
     private final Map<String, Class<? extends Plugin>> idMappings = new HashMap<String, Class<? extends Plugin>>();
     private final DefaultPluginRegistry parent;
     private final ClassLoader classLoader;
+    private final Instantiator instantiator;
 
-    public DefaultPluginRegistry(ClassLoader classLoader) {
-        this(null, classLoader);
+    public DefaultPluginRegistry(ClassLoader classLoader, Instantiator instantiator) {
+        this(null, classLoader, instantiator);
     }
 
-    private DefaultPluginRegistry(DefaultPluginRegistry parent, ClassLoader classLoader) {
+    private DefaultPluginRegistry(DefaultPluginRegistry parent, ClassLoader classLoader, Instantiator instantiator) {
         this.parent = parent;
         this.classLoader = classLoader;
+        this.instantiator = instantiator;
     }
 
-    public PluginRegistry createChild(ClassLoader childClassPath) {
-        return new DefaultPluginRegistry(this, childClassPath);
+    public PluginRegistry createChild(ClassLoader childClassPath, Instantiator instantiator) {
+        return new DefaultPluginRegistry(this, childClassPath, instantiator);
     }
 
     public <T extends Plugin> T loadPlugin(Class<T> pluginClass) {
-        if (parent != null) {
-            return parent.loadPlugin(pluginClass);
-        }
-
         if (!Plugin.class.isAssignableFrom(pluginClass)) {
             throw new InvalidUserDataException(String.format(
                     "Cannot create plugin of type '%s' as it does not implement the Plugin interface.",
                     pluginClass.getSimpleName()));
         }
         try {
-            return pluginClass.newInstance();
-        } catch (InstantiationException e) {
+            return instantiator.newInstance(pluginClass);
+        } catch (ObjectInstantiationException e) {
             throw new PluginInstantiationException(String.format("Could not create plugin of type '%s'.",
                     pluginClass.getSimpleName()), e.getCause());
-        } catch (Exception e) {
-            throw new PluginInstantiationException(String.format("Could not create plugin of type '%s'.",
-                    pluginClass.getSimpleName()), e);
         }
     }
 
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 407601d..b14bd5f 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
@@ -19,6 +19,7 @@ package org.gradle.api.internal.plugins;
 import org.gradle.api.Plugin;
 import org.gradle.api.plugins.PluginInstantiationException;
 import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.internal.reflect.Instantiator;
 
 /**
  * @author Hans Dockter
@@ -28,5 +29,5 @@ public interface PluginRegistry {
 
     Class<? extends Plugin> getTypeForId(String pluginId) throws UnknownPluginException, PluginInstantiationException;
 
-    PluginRegistry createChild(ClassLoader childClassPath);
+    PluginRegistry createChild(ClassLoader childClassPath, Instantiator instantiator);
 }
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 e4d5b45..c5972e1 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
@@ -241,7 +241,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public void setScript(Script buildScript) {
-        extensibleDynamicObject.addObject(new BeanDynamicObject(buildScript).withNoProperties(),
+        extensibleDynamicObject.addObject(new BeanDynamicObject(buildScript).withNoProperties().withNotImplementsMissing(),
                 ExtensibleDynamicObject.Location.BeforeConvention);
     }
 
@@ -680,7 +680,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     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");
+        DeprecationLogger.nagUserOfDeprecated("fileTree(Closure)", "Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
         return fileOperations.fileTree(closure);
     }
 
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
index e247fbb..1f4c673 100755
--- 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
@@ -47,12 +47,12 @@ import org.gradle.util.DefaultClassLoaderFactory;
  */
 public class GlobalServicesRegistry extends DefaultServiceRegistry {
     public GlobalServicesRegistry() {
-        this(LoggingServiceRegistry.newCommandLineProcessLogging());
+        this(LoggingServiceRegistry.newProcessLogging());
     }
 
     public GlobalServicesRegistry(ServiceRegistry loggingServices) {
         super(loggingServices);
-        add(new NativeServices());
+        add(NativeServices.getInstance());
     }
 
     protected CommandLineConverter<StartParameter> createCommandLine2StartParameterConverter() {
@@ -67,6 +67,10 @@ public class GlobalServicesRegistry extends DefaultServiceRegistry {
         return new DefaultModuleRegistry();
     }
 
+    protected DocumentationRegistry createDocumentationRegistry() {
+        return new DocumentationRegistry(get(GradleDistributionLocator.class));
+    }
+
     protected PluginModuleRegistry createPluginModuleRegistry() {
         return new DefaultPluginModuleRegistry(get(ModuleRegistry.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 f1c7589..2413616 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,14 +15,17 @@
  */
 package org.gradle.api.internal.project;
 
+import org.gradle.api.internal.DependencyInjectingInstantiator;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 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.internal.service.DefaultServiceRegistry;
 import org.gradle.execution.*;
+import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter;
+import org.gradle.execution.taskgraph.TaskPlanExecutor;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 
@@ -63,11 +66,11 @@ public class GradleInternalServiceRegistry extends DefaultServiceRegistry implem
     }
 
     protected TaskGraphExecuter createTaskGraphExecuter() {
-        return new DefaultTaskGraphExecuter(get(ListenerManager.class));
+        return new DefaultTaskGraphExecuter(get(ListenerManager.class), get(TaskPlanExecutor.class));
     }
 
     protected PluginRegistry createPluginRegistry() {
-        return new DefaultPluginRegistry(gradle.getScriptClassLoader());
+        return new DefaultPluginRegistry(gradle.getScriptClassLoader(), new DependencyInjectingInstantiator(this));
     }
 
     public ServiceRegistryFactory createFor(Object domainObject) {
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 01a5ba6..58590bb 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
@@ -21,7 +21,9 @@ 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.internal.reflect.Instantiator;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator;
+import org.gradle.api.internal.DependencyInjectingInstantiator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.DefaultModule;
@@ -44,6 +46,7 @@ import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.plugins.PluginContainer;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
@@ -62,7 +65,7 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
-        return parentRegistry.createChild(get(ScriptClassLoaderProvider.class).getClassLoader());
+        return parentRegistry.createChild(get(ScriptClassLoaderProvider.class).getClassLoader(), new DependencyInjectingInstantiator(this));
     }
 
     protected FileResolver createFileResolver() {
@@ -93,11 +96,14 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
         return new DefaultProjectsPluginContainer(get(PluginRegistry.class), project);
     }
 
+    protected ITaskFactory createTaskFactory(ITaskFactory parentFactory) {
+        return parentFactory.createChild(project, new ClassGeneratorBackedInstantiator(get(ClassGenerator.class), new DependencyInjectingInstantiator(this)));
+    }
+
     protected Factory<TaskContainerInternal> createTaskContainerInternal() {
         return new DefaultTaskContainerFactory(get(Instantiator.class), get(ITaskFactory.class), project);
     }
 
-    //TODO SF what's going on here?
     protected Factory<ArtifactPublicationServices> createRepositoryHandlerFactory() {
         return get(DependencyResolutionServices.class).getPublishServicesFactory();
     }
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
index 3b02320..b465b93 100644
--- 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
@@ -17,13 +17,16 @@ package org.gradle.api.internal.project;
 
 import org.gradle.StartParameter;
 import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.changedetection.*;
-import org.gradle.internal.id.RandomLongIdGenerator;
-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.execution.taskgraph.TaskPlanExecutor;
+import org.gradle.execution.taskgraph.TaskPlanExecutorFactory;
+import org.gradle.internal.id.RandomLongIdGenerator;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 
@@ -75,4 +78,10 @@ public class TaskExecutionServices extends DefaultServiceRegistry {
                                 outputFilesSnapshotter)),
                 new DefaultFileCacheListener());
     }
+
+    protected TaskPlanExecutor createTaskExecutorFactory() {
+        StartParameter startParameter = gradle.getStartParameter();
+        TaskArtifactStateCacheAccess cacheAccess = get(TaskArtifactStateCacheAccess.class);
+        return new TaskPlanExecutorFactory(cacheAccess, startParameter.getParallelThreadCount(), get(DocumentationRegistry.class)).create();
+    }
 }
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 7d223ce..bd63916 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
@@ -63,11 +63,11 @@ 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 org.gradle.util.ClassLoaderFactory;
+import org.gradle.util.MultiParentClassLoader;
 
 /**
- * Contains the singleton services which are shared by all builds executed by a single {@link org.gradle.GradleLauncher}
- * invocation.
+ * 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;
@@ -85,7 +85,7 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     protected TimeProvider createTimeProvider() {
         return new TrueTimeProvider();
     }
-    
+
     protected ExecutorFactory createExecutorFactory() {
         return new DefaultExecutorFactory();
     }
@@ -132,7 +132,7 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
         return new DefaultCacheRepository(startParameter.getGradleUserHomeDir(), startParameter.getProjectCacheDir(),
                 startParameter.getCacheUsage(), factory);
     }
-    
+
     protected ProjectEvaluator createProjectEvaluator() {
         return new LifecycleProjectEvaluator(
                 new BuildScriptProcessor(
@@ -149,7 +149,7 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     protected ScriptCompilerFactory createScriptCompileFactory() {
         ScriptExecutionListener scriptExecutionListener = get(ListenerManager.class).getBroadcaster(ScriptExecutionListener.class);
         EmptyScriptGenerator emptyScriptGenerator = new AsmBackedEmptyScriptGenerator();
-        CacheValidator scriptCacheInvalidator =  new CacheValidator() {
+        CacheValidator scriptCacheInvalidator = new CacheValidator() {
             public boolean isValid() {
                 return !get(StartParameter.class).isRecompileScripts();
             }
@@ -178,7 +178,7 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     protected MultiParentClassLoader createRootClassLoader() {
         return get(ClassLoaderRegistry.class).createScriptClassLoader();
     }
-    
+
     protected InitScriptHandler createInitScriptHandler() {
         return new InitScriptHandler(
                 new CompositeInitScriptFinder(
@@ -194,15 +194,17 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     protected SettingsProcessor createSettingsProcessor() {
         return new PropertiesLoadingSettingsProcessor(
                 new ScriptEvaluatingSettingsProcessor(
-                    get(ScriptPluginFactory.class),
-                    new SettingsFactory(
-                        new DefaultProjectDescriptorRegistry()),
+                        get(ScriptPluginFactory.class),
+                        new SettingsFactory(
+                                new DefaultProjectDescriptorRegistry(),
+                                get(Instantiator.class)
+                        ),
                         get(IGradlePropertiesLoader.class)),
                 get(IGradlePropertiesLoader.class));
     }
 
     protected ExceptionAnalyser createExceptionAnalyser() {
-        return new DefaultExceptionAnalyser(get(ListenerManager.class));
+        return new MultipleBuildFailuresExceptionAnalyser(new DefaultExceptionAnalyser(get(ListenerManager.class)));
     }
 
     protected ScriptHandlerFactory createScriptHandlerFactory() {
@@ -228,7 +230,7 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
                 new ProjectDependencies2TaskResolver(),
                 new ImplicitTasksConfigurer());
     }
-    
+
     protected ProfileEventAdapter createProfileEventAdapter() {
         return new ProfileEventAdapter(get(BuildRequestMetaData.class), get(TimeProvider.class), get(ListenerManager.class).getBroadcaster(ProfileListener.class));
     }
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 0a6c305..eedcb7d 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
@@ -24,6 +24,7 @@ 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.*;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ReflectionUtil;
 
 import java.io.File;
@@ -40,7 +41,7 @@ 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 Map<Class, List<Action<Task>>> actionsForType;
     
     private final Transformer<Iterable<File>, Object> filePropertyTransformer = new Transformer<Iterable<File>, Object>() {
         public Iterable<File> transform(Object original) {
@@ -76,10 +77,20 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
 
     public AnnotationProcessingTaskFactory(ITaskFactory taskFactory) {
         this.taskFactory = taskFactory;
+        this.actionsForType = new HashMap<Class, List<Action<Task>>>();
     }
 
-    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
-        TaskInternal task = taskFactory.createTask(project, args);
+    private AnnotationProcessingTaskFactory(Map<Class, List<Action<Task>>> actionsForType, ITaskFactory taskFactory) {
+        this.actionsForType = actionsForType;
+        this.taskFactory = taskFactory;
+    }
+
+    public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator) {
+        return new AnnotationProcessingTaskFactory(actionsForType, taskFactory.createChild(project, instantiator));
+    }
+
+    public TaskInternal createTask(Map<String, ?> args) {
+        TaskInternal task = taskFactory.createTask(args);
 
         Class<? extends Task> type = task.getClass();
         List<Action<Task>> actions = actionsForType.get(type);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java
index 9a8c17a..3976acb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java
@@ -17,34 +17,27 @@ package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
 
-import java.util.HashMap;
 import java.util.Map;
 
 /**
  * A {@link ITaskFactory} which wires the dependencies of a task based on its input files.
  */
 public class DependencyAutoWireTaskFactory implements ITaskFactory {
-    public static final String DEPENDENCY_AUTO_WIRE = "dependencyAutoWire";
     private final ITaskFactory taskFactory;
 
     public DependencyAutoWireTaskFactory(ITaskFactory taskFactory) {
         this.taskFactory = taskFactory;
     }
 
-    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
-        Map<String, Object> actualArgs = new HashMap<String, Object>(args);
-        boolean autoWire = remove(actualArgs, DEPENDENCY_AUTO_WIRE);
-
-        TaskInternal task = taskFactory.createTask(project, actualArgs);
-        if (autoWire) {
-            task.dependsOn(task.getInputs().getFiles());
-        }
-        return task;
+    public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator) {
+        return new DependencyAutoWireTaskFactory(taskFactory.createChild(project, instantiator));
     }
 
-    private boolean remove(Map<String, ?> args, String key) {
-        Object value = args.remove(key);
-        return value == null ? true : Boolean.valueOf(value.toString());
+    public TaskInternal createTask(Map<String, ?> args) {
+        TaskInternal task = taskFactory.createTask(args);
+        task.dependsOn(task.getInputs().getFiles());
+        return task;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java
index f292891..f2dc175 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.Map;
 
@@ -24,5 +25,7 @@ import java.util.Map;
  * @author Hans Dockter
  */
 public interface ITaskFactory {
-    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args);
+    public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator);
+
+    public TaskInternal createTask(Map<String, ?> args);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java
index 10368f5..16874d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java
@@ -16,16 +16,19 @@
 package org.gradle.api.internal.project.taskfactory;
 
 import groovy.lang.Closure;
-import org.gradle.api.*;
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
 import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.ClassGenerator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.TaskInstantiationException;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.gradle.util.GUtil;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -36,12 +39,24 @@ import java.util.concurrent.Callable;
 public class TaskFactory implements ITaskFactory {
     public static final String GENERATE_SUBCLASS = "generateSubclass";
     private final ClassGenerator generator;
+    private final ProjectInternal project;
+    private final Instantiator instantiator;
 
     public TaskFactory(ClassGenerator generator) {
+        this(generator, null, null);
+    }
+
+    TaskFactory(ClassGenerator generator, ProjectInternal project, Instantiator instantiator) {
         this.generator = generator;
+        this.project = project;
+        this.instantiator = instantiator;
+    }
+
+    public ITaskFactory createChild(ProjectInternal project, Instantiator instantiator) {
+        return new TaskFactory(generator, project, instantiator);
     }
 
-    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
+    public TaskInternal createTask(Map<String, ?> args) {
         Map<String, Object> actualArgs = new HashMap<String, Object>(args);
         checkTaskArgsAndCreateDefaultValues(actualArgs);
 
@@ -85,34 +100,20 @@ public class TaskFactory implements ITaskFactory {
                     type.getSimpleName()));
         }
 
-        Class<? extends TaskInternal> generatedType;
+        final Class<? extends TaskInternal> generatedType;
         if (generateGetters) {
             generatedType = generator.generate(type);
         } else {
             generatedType = type;
         }
 
-        final Constructor<? extends TaskInternal> constructor;
-        final Object[] params;
-        try {
-            constructor = generatedType.getDeclaredConstructor();
-            params = new Object[0];
-        } catch (NoSuchMethodException e) {
-            // Ignore
-            throw new InvalidUserDataException(String.format(
-                    "Cannot create task of type '%s' as it does not have a public no-args constructor.",
-                    type.getSimpleName()));
-        }
-
         return AbstractTask.injectIntoNewInstance(project, name, new Callable<TaskInternal>() {
             public TaskInternal call() throws Exception {
                 try {
-                    return constructor.newInstance(params);
-                } catch (InvocationTargetException e) {
+                    return instantiator.newInstance(generatedType);
+                } catch (ObjectInstantiationException e) {
                     throw new TaskInstantiationException(String.format("Could not create task of type '%s'.", type.getSimpleName()),
                             e.getCause());
-                } catch (Exception e) {
-                    throw new TaskInstantiationException(String.format("Could not create task of type '%s'.", type.getSimpleName()), e);
                 }
             }
         });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java
index 15e94ac..923578a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java
@@ -23,4 +23,8 @@ public class ResourceNotFoundException extends ResourceException {
     public ResourceNotFoundException(String message) {
         super(message);
     }
+
+    public ResourceNotFoundException(String message, Throwable t) {
+        super(message, t);
+    }
 }
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 9fb0794..cd57b20 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
@@ -17,13 +17,17 @@
 package org.gradle.api.internal.resource;
 
 import org.apache.commons.io.IOUtils;
+import org.gradle.internal.SystemProperties;
+import org.gradle.util.GradleVersion;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.net.URLConnection;
 
-import static org.gradle.util.GFileUtils.*;
+import static org.gradle.util.GFileUtils.canonicalise;
 
 public class UriResource implements Resource {
     private final File sourceFile;
@@ -51,7 +55,7 @@ public class UriResource implements Resource {
             throw new ResourceException(String.format("Could not read %s as it is a directory.", getDisplayName()));
         }
         try {
-            InputStream inputStream = sourceUri.toURL().openStream();
+            InputStream inputStream = getInputStream(sourceUri);
             try {
                 return IOUtils.toString(inputStream);
             } finally {
@@ -66,7 +70,7 @@ public class UriResource implements Resource {
 
     public boolean getExists() {
         try {
-            InputStream inputStream = sourceUri.toURL().openStream();
+            InputStream inputStream = getInputStream(sourceUri);
             try {
                 return true;
             } finally {
@@ -79,6 +83,12 @@ public class UriResource implements Resource {
         }
     }
 
+    private InputStream getInputStream(URI url) throws IOException {
+        final URLConnection urlConnection = url.toURL().openConnection();
+        urlConnection.setRequestProperty("User-Agent", getUserAgentString());
+        return urlConnection.getInputStream();
+    }
+
     public File getFile() {
         return sourceFile;
     }
@@ -86,4 +96,22 @@ public class UriResource implements Resource {
     public URI getURI() {
         return sourceUri;
     }
+
+    public static String getUserAgentString() {
+        String osName = System.getProperty("os.name");
+        String osVersion = System.getProperty("os.version");
+        String osArch = System.getProperty("os.arch");
+        String javaVendor = System.getProperty("java.vendor");
+        String javaVersion = SystemProperties.getJavaVersion();
+        String javaVendorVersion = System.getProperty("java.vm.version");
+        return String.format("Gradle/%s (%s;%s;%s) (%s;%s;%s)",
+                GradleVersion.current().getVersion(),
+                osName,
+                osVersion,
+                osArch,
+                javaVendor,
+                javaVersion,
+                javaVendorVersion);
+    }
+
 }
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 ca19b47..7fb384e 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
@@ -22,10 +22,10 @@ import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.UnknownTaskException;
 import org.gradle.api.internal.DynamicObject;
-import org.gradle.internal.reflect.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.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GUtil;
@@ -47,7 +47,7 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         Object replaceStr = mutableOptions.remove(Task.TASK_OVERWRITE);
         boolean replace = replaceStr != null && "true".equals(replaceStr.toString());
 
-        Task task = taskFactory.createTask(project, mutableOptions);
+        Task task = taskFactory.createTask(mutableOptions);
         String name = task.getName();
 
         Task existing = findByNameWithoutRules(name);
@@ -118,9 +118,10 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
             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()));
+            DeprecationLogger.nagUserOfDeprecated(
+                    String.format("Converting class %s to a task dependency using toString()", path.getClass().getName()),
+                    "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"
+            );
         }
         return getByPath(path.toString());
     }
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 afecbf2..da96142 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
@@ -22,6 +22,7 @@ import org.gradle.api.Action;
 import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.internal.HasInternalProtocol;
 
 import java.io.File;
 
@@ -30,6 +31,7 @@ import java.io.File;
  *
  * <p>You can obtain a {@code Gradle} instance by calling {@link Project#getGradle()}.</p>
  */
+ at HasInternalProtocol
 public interface Gradle {
     /**
      * <p>Returns the current Gradle version.</p>
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
index 3d51df9..c905d04 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionAware.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionAware.java
@@ -43,7 +43,7 @@ package org.gradle.api.plugins;
  * }
  * assert project.custom.foo == "other"
  *
- * // Extensions added with the extensnion container's create method are themselves extensible
+ * // Extensions added with the extension 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"
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
new file mode 100644
index 0000000..49af033
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.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.api.publish.ivy.internal;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.XmlTransformer;
+
+import java.io.File;
+import java.util.Set;
+
+public class IvyNormalizedPublication {
+
+    private final Module module;
+    private final File descriptorFile;
+    private final XmlTransformer descriptorTransformer;
+    private final Set<? extends Configuration> configurations;
+
+    public IvyNormalizedPublication(Module module, Set<? extends Configuration> configurations, File descriptorFile, XmlTransformer descriptorTransformer) {
+        this.module = module;
+        this.configurations = configurations;
+        this.descriptorFile = descriptorFile;
+        this.descriptorTransformer = descriptorTransformer;
+    }
+
+    public Module getModule() {
+        return module;
+    }
+
+    public Set<? extends Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    public File getDescriptorFile() {
+        return descriptorFile;
+    }
+
+    public XmlTransformer getDescriptorTransformer() {
+        return descriptorTransformer;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.java b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.java
new file mode 100644
index 0000000..7810a97
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/publish/ivy/internal/IvyPublisher.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.publish.ivy.internal;
+
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+
+public class IvyPublisher {
+
+    private final ArtifactPublisher artifactPublisher;
+
+    public IvyPublisher(ArtifactPublisher artifactPublisher) {
+        this.artifactPublisher = artifactPublisher;
+    }
+
+    public void publish(IvyNormalizedPublication publication) {
+        artifactPublisher.publish(publication.getModule(), publication.getConfigurations(), publication.getDescriptorFile(), publication.getDescriptorTransformer());
+    }
+
+}
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
deleted file mode 100644
index 2cfa857..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/AndSpec.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.api.specs;
-
-import com.google.common.collect.Iterables;
-import groovy.lang.Closure;
-
-import java.util.Arrays;
-
-/**
- * A {@link org.gradle.api.specs.CompositeSpec} which requires all its specs to be true in order to evaluate to true.
- * Uses lazy evaluation.
- *
- * @author Hans Dockter
- * @param <T> The target type for this Spec
- */
-public class AndSpec<T> extends CompositeSpec<T> {
-    public AndSpec(Spec<? super T>... specs) {
-        super(specs);
-    }
-
-    public AndSpec(Iterable<? extends Spec<? super T>> specs) {
-        super(specs);
-    }
-
-    public boolean isSatisfiedBy(T object) {
-        for (Spec<? super T> spec : getSpecs()) {
-            if (!spec.isSatisfiedBy(object)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public AndSpec<T> and(Spec<? super T>... specs) {
-        return new AndSpec<T>(Iterables.concat(getSpecs(), Arrays.asList(specs)));
-    }
-
-    public AndSpec<T> and(Closure spec) {
-        return and(Specs.<T>convertClosureToSpec(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
deleted file mode 100644
index 4aff0ad..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
+++ /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.api.specs;
-
-import com.google.common.collect.Lists;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link org.gradle.api.specs.Spec} which aggregates a sequence of other {@code Spec} instances.
- *
- * @author Hans Dockter
- * @param <T> The target type for this Spec
- */
-abstract public class CompositeSpec<T> implements Spec<T> {
-    private List<Spec<? super T>> specs;
-
-    protected CompositeSpec(Spec<? super T>... specs) {
-        this.specs = Lists.newArrayList(specs);
-    }
-
-    protected CompositeSpec(Iterable<? extends Spec<? super T>> specs) {
-        this.specs = Lists.newArrayList(specs);
-    }
-
-    public List<Spec<? super T>> getSpecs() {
-        return Collections.unmodifiableList(specs);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof CompositeSpec)) {
-            return false;
-        }
-
-        CompositeSpec that = (CompositeSpec) o;
-
-        if (specs != null ? !specs.equals(that.specs) : that.specs != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return specs != null ? specs.hashCode() : 0;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/NotSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/NotSpec.java
deleted file mode 100644
index 1e2ac91..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/NotSpec.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.specs;
-
-/**
- * A {@link Spec} implementation which negates another {@code Spec}.
- * 
- * @author Hans Dockter
- * @param <T> The target type for this Spec
- */
-public class NotSpec<T> implements Spec<T> {
-    private Spec<? super T> sourceSpec;
-
-    public NotSpec(Spec<? super T> sourceSpec) {
-        this.sourceSpec = sourceSpec;
-    }
-
-    public boolean isSatisfiedBy(T element) {
-        return !sourceSpec.isSatisfiedBy(element);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/OrSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/OrSpec.java
deleted file mode 100644
index e108522..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/OrSpec.java
+++ /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.specs;
-
-import java.util.List;
-
-/**
- * A {@link org.gradle.api.specs.CompositeSpec} which requires any one of its specs to be true in order to evaluate to
- * true. Uses lazy evaluation.
- *
- * @author Hans Dockter
- * @param <T> The target type for this Spec
- */
-public class OrSpec<T> extends CompositeSpec<T> {
-    public OrSpec(Spec<? super T>... specs) {
-        super(specs);
-    }
-
-    public boolean isSatisfiedBy(T object) {
-        List<Spec<? super T>> specs = getSpecs();
-        if (specs.isEmpty()) {
-            return true;
-        }
-
-        for (Spec<? super T> spec : specs) {
-            if (spec.isSatisfiedBy(object)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
\ No newline at end of file
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 e480735..bf6c8d8 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,21 +15,25 @@
  */
 package org.gradle.api.specs;
 
+import groovy.lang.Closure;
 import org.gradle.api.specs.internal.ClosureSpec;
 import org.gradle.util.DeprecationLogger;
 
-import groovy.lang.Closure;
-
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Provides a number of {@link Spec} implementations.
+ * Provides a number of {@link org.gradle.api.specs.Spec} implementations.
  *
  * @author Hans Dockter
  */
 public class Specs {
+
+    /*
+        Note: This should be in baseServicesGroovy, but it needs the DeprecationLogger which needs commons-lang
+              It as
+     */
     public static final Spec<Object> SATISFIES_ALL = new Spec<Object>() {
         public boolean isSatisfiedBy(Object element) {
             return true;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/package-info.java
index ffe2a9f..a5cc802 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/package-info.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/package-info.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.
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 dc9a040..a3a6f4b 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
@@ -29,7 +29,7 @@ public class Directory extends DefaultTask {
     File dir
     
     Directory() {
-        DeprecationLogger.nagUserOfReplacedTask("Directory", "Project.mkdir(java.lang.Object)");
+        DeprecationLogger.nagUserOfReplacedTaskType("Directory", "Project.mkdir(java.lang.Object) method");
         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 4d95679..13b2e11 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
@@ -38,8 +38,16 @@ import java.util.Map;
  *   //on windows:
  *   commandLine 'cmd', '/c', 'stop.bat'
  *
- *   //on linux (oh yeah!!!)
+ *   //on linux
  *   commandLine './stop.sh'
+ *
+ *   //store the output instead of printing to the console:
+ *   standardOutput = new ByteArrayOutputStream()
+ *
+ *   //extension method stopTomcat.output() can be used to obtain the output:
+ *   ext.output = {
+ *     return standardOutput.toString()
+ *   }
  * }
  * </pre>
  * 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
index e90959e..5f3659c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
@@ -76,7 +76,7 @@ public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectCo
      * <p>After the task is added, 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="../Project.html#properties">here</a> for more details.</p>
      *
-     * <p>If a task with the given name already exists in this container and the <code>override</code> option is not set
+     * <p>If a task with the given name already exists in this container and the <code>overwrite</code> option is not set
      * to true, an exception is thrown.</p>
      *
      * @param options The task creation options.
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 9e37b65..7087bc3 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
@@ -60,7 +60,12 @@ public class Upload extends ConventionTask {
     @TaskAction
     protected void upload() {
         logger.info("Publishing configuration: " + configuration);
-        artifactPublisher.publish((ConfigurationInternal) configuration, isUploadDescriptor() ? getDescriptorDestination() : null);
+        ConfigurationInternal configurationInternal = (ConfigurationInternal) configuration;
+        artifactPublisher.publish(
+                configurationInternal.getModule(), configuration.getHierarchy(),
+                isUploadDescriptor() ? getDescriptorDestination() : null,
+                null
+        );
     }
 
     /**
@@ -128,4 +133,5 @@ public class Upload extends ConventionTask {
     void setArtifactPublisher(ArtifactPublisher artifactPublisher) {
         this.artifactPublisher = artifactPublisher;
     }
+
 }
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
deleted file mode 100644
index 10af158..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
+++ /dev/null
@@ -1,90 +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.tasks.diagnostics;
-
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.tasks.diagnostics.internal.AsciiReportRenderer;
-import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
-import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
-
-import java.io.IOException;
-import java.util.Comparator;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * Displays the dependency tree for a project. Can be configured to output to a file, and to optionally output a
- * graphviz compatible "dot" graph. An instance of this type is used when you execute the {@code dependencies} task from
- * the command-line.
- *
- * @author Phil Messenger
- */
-public class DependencyReportTask extends AbstractReportTask {
-
-    private DependencyReportRenderer renderer = new AsciiReportRenderer();
-
-    private Set<Configuration> configurations;
-
-    public ReportRenderer getRenderer() {
-        return renderer;
-    }
-
-    /**
-     * Set the renderer to use to build a report. If unset, AsciiGraphRenderer will be used.
-     */
-    public void setRenderer(DependencyReportRenderer renderer) {
-        this.renderer = renderer;
-    }
-
-    public void generate(Project project) throws IOException {
-        SortedSet<Configuration> sortedConfigurations = new TreeSet<Configuration>(new Comparator<Configuration>() {
-            public int compare(Configuration conf1, Configuration conf2) {
-                return conf1.getName().compareTo(conf2.getName());
-            }
-        });
-        sortedConfigurations.addAll(getConfigurations(project));
-        for (Configuration configuration : sortedConfigurations) {
-            renderer.startConfiguration(configuration);
-            renderer.render(configuration.getResolvedConfiguration());
-            renderer.completeConfiguration(configuration);
-        }
-    }
-
-    private Set<Configuration> getConfigurations(Project project) {
-        return configurations != null ? configurations : project.getConfigurations();
-    }
-
-    /**
-     * Returns the configurations to generate the report for. Default to all configurations of this task's containing
-     * project.
-     *
-     * @return the configurations.
-     */
-    public Set<Configuration> getConfigurations() {
-        return configurations;
-    }
-
-    /**
-     * Sets the configurations to generate the report for.
-     *
-     * @param configurations The configuration. Must not be null.
-     */
-    public void setConfigurations(Set<Configuration> configurations) {
-        this.configurations = configurations;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
deleted file mode 100644
index bdfc43e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
+++ /dev/null
@@ -1,50 +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.tasks.diagnostics;
-
-import org.gradle.api.Project;
-import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
-import org.gradle.api.tasks.diagnostics.internal.PropertyReportRenderer;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Displays the properties of a project. An instance of this type is used when you execute the {@code properties} task
- * from the command-line.
- */
-public class PropertyReportTask extends AbstractReportTask {
-    private PropertyReportRenderer renderer = new PropertyReportRenderer();
-
-    public ReportRenderer getRenderer() {
-        return renderer;
-    }
-
-    public void setRenderer(PropertyReportRenderer renderer) {
-        this.renderer = renderer;
-    }
-
-    public void generate(Project project) throws IOException {
-        for (Map.Entry<String, ?> entry : new TreeMap<String, Object>(project.getProperties()).entrySet()) {
-            if (entry.getKey().equals("properties")) {
-                renderer.addProperty(entry.getKey(), "{...}");
-            } else {
-                renderer.addProperty(entry.getKey(), entry.getValue());
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java
deleted file mode 100644
index 4e41cb1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java
+++ /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.api.tasks.diagnostics.internal;
-
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.TreeMultimap;
-import org.gradle.util.Path;
-
-import java.util.*;
-
-public class AggregateMultiProjectTaskReportModel implements TaskReportModel {
-    private List<TaskReportModel> projects = new ArrayList<TaskReportModel>();
-    private SetMultimap<String, TaskDetails> groups;
-    private final boolean mergeTasksWithSameName;
-
-    public AggregateMultiProjectTaskReportModel(boolean mergeTasksWithSameName) {
-        this.mergeTasksWithSameName = mergeTasksWithSameName;
-    }
-
-    public void add(TaskReportModel project) {
-        projects.add(project);
-    }
-
-    public void build() {
-        groups = TreeMultimap.create(new Comparator<String>() {
-            public int compare(String string1, String string2) {
-                return string1.compareToIgnoreCase(string2);
-            }
-        }, new Comparator<TaskDetails>() {
-            public int compare(TaskDetails task1, TaskDetails task2) {
-                return task1.getPath().compareTo(task2.getPath());
-            }
-        });
-        for (TaskReportModel project : projects) {
-            for (String group : project.getGroups()) {
-                for (final TaskDetails task : project.getTasksForGroup(group)) {
-                    groups.put(group, mergeTasksWithSameName ? new MergedTaskDetails(task) : task);
-                }
-            }
-        }
-    }
-
-    public Set<String> getGroups() {
-        return groups.keySet();
-    }
-
-    public Set<TaskDetails> getTasksForGroup(String group) {
-        return groups.get(group);
-    }
-
-    private static class MergedTaskDetails implements TaskDetails {
-        private final TaskDetails task;
-
-        public MergedTaskDetails(TaskDetails task) {
-            this.task = task;
-        }
-
-        public Path getPath() {
-            return Path.path(task.getPath().getName());
-        }
-
-        public Set<TaskDetails> getChildren() {
-            return task.getChildren();
-        }
-
-        public String getDescription() {
-            return task.getDescription();
-        }
-
-        public Set<TaskDetails> getDependencies() {
-            return task.getDependencies();
-        }
-    }
-}
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
deleted file mode 100644
index 26a2018..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRenderer.java
+++ /dev/null
@@ -1,172 +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.tasks.diagnostics.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.logging.StyledTextOutput;
-import org.gradle.util.GUtil;
-
-import java.io.IOException;
-import java.util.*;
-
-import static org.gradle.logging.StyledTextOutput.Style.*;
-
-/**
- * Simple dependency graph renderer that emits an ASCII tree.
- *
- * @author Phil Messenger
- */
-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
-    public void completeProject(Project project) {
-        if (!hasConfigs) {
-            getTextOutput().withStyle(Info).println("No configurations");
-        }
-        super.completeProject(project);
-    }
-
-    public void startConfiguration(final Configuration configuration) {
-        if (hasConfigs) {
-            getTextOutput().println();
-        }
-        hasConfigs = true;
-        renderer = new GraphRenderer(getTextOutput());
-        renderer.visit(new Action<StyledTextOutput>() {
-            public void execute(StyledTextOutput styledTextOutput) {
-                getTextOutput().withStyle(Identifier).text(configuration.getName());
-                getTextOutput().withStyle(Description).text(getDescription(configuration));
-            }
-        }, true);
-    }
-
-    private String getDescription(Configuration configuration) {
-        return GUtil.isTrue(configuration.getDescription()) ? " - " + configuration.getDescription() : "";
-    }
-
-    public void completeConfiguration(Configuration configuration) {
-    }
-
-    public void render(ResolvedConfiguration resolvedConfiguration) throws IOException {
-        Set<MergedResolvedDependency> mergedRoots = mergeChildren(resolvedConfiguration.getFirstLevelModuleDependencies());
-        if (mergedRoots.isEmpty()) {
-            getTextOutput().withStyle(Info).text("No dependencies");
-            getTextOutput().println();
-            return;
-        }
-        renderChildren(mergedRoots, new HashSet<String>());
-    }
-
-    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());
-                StyledTextOutput infoStyle = getTextOutput().withStyle(Info);
-                infoStyle.format(" [%s]", resolvedDependency.getConfiguration());
-
-                if (!isFirstVisitOfDependencyInConfiguration) {
-                    infoStyle.append(" (*)");
-                }
-            }
-        }, lastChild);
-
-        if (isFirstVisitOfDependencyInConfiguration) {
-            renderChildren(mergeChildren(resolvedDependency.getChildren()), visitedDependencyNames);
-        }
-    }
-
-    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, visitedDependencyNames, i == mergedChildren.size() - 1);
-        }
-        renderer.completeChildren();
-    }
-
-    private Set<MergedResolvedDependency> mergeChildren(Set<ResolvedDependency> children) {
-        Map<String, Set<ResolvedDependency>> mergedGroups = new LinkedHashMap<String, Set<ResolvedDependency>>();
-        for (ResolvedDependency child : children) {
-            Set<ResolvedDependency> mergeGroup = mergedGroups.get(child.getName());
-            if (mergeGroup == null) {
-                mergedGroups.put(child.getName(), mergeGroup = new LinkedHashSet<ResolvedDependency>());
-            }
-            mergeGroup.add(child);
-        }
-        Set<MergedResolvedDependency> mergedChildren = new LinkedHashSet<MergedResolvedDependency>();
-        for (Set<ResolvedDependency> mergedGroup : mergedGroups.values()) {
-            mergedChildren.add(new MergedResolvedDependency(mergedGroup));
-        }
-        return mergedChildren;
-    }
-
-    private static class MergedResolvedDependency {
-        private Set<ResolvedDependency> mergedResolvedDependencies = new LinkedHashSet<ResolvedDependency>();
-
-        public MergedResolvedDependency(Set<ResolvedDependency> mergedResolvedDependencies) {
-            assert !mergedResolvedDependencies.isEmpty();
-            this.mergedResolvedDependencies = mergedResolvedDependencies;
-        }
-
-        public String getName() {
-            return mergedResolvedDependencies.iterator().next().getName();
-        }
-
-        public String getConfiguration() {
-            String mergedConfiguration = "";
-            for (ResolvedDependency mergedResolvedDependency : mergedResolvedDependencies) {
-                mergedConfiguration += mergedResolvedDependency.getConfiguration() + ",";
-            }
-            return mergedConfiguration.substring(0, mergedConfiguration.length() - 1);
-        }
-
-        public Set<ResolvedDependency> getChildren() {
-            Set<ResolvedDependency> mergedChildren = new LinkedHashSet<ResolvedDependency>();
-            for (ResolvedDependency mergedResolvedDependency : mergedResolvedDependencies) {
-                mergedChildren.addAll(mergedResolvedDependency.getChildren());
-            }
-            return mergedChildren;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.java
deleted file mode 100644
index ca009c2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.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.tasks.diagnostics.internal;
-
-import com.google.common.collect.SetMultimap;
-import com.google.common.collect.TreeMultimap;
-import org.gradle.util.GUtil;
-import org.gradle.util.Path;
-
-import java.util.Comparator;
-import java.util.Set;
-
-public class DefaultGroupTaskReportModel implements TaskReportModel {
-    public static final String OTHER_GROUP = "other";
-    private static final Comparator<String> STRING_COMPARATOR = GUtil.caseInsensitive();
-    private SetMultimap<String, TaskDetails> groups;
-
-    public void build(TaskReportModel model) {
-        Comparator<String> keyComparator = GUtil.last(GUtil.last(STRING_COMPARATOR, OTHER_GROUP), TaskReportModel.DEFAULT_GROUP);
-        Comparator<TaskDetails> taskComparator = new Comparator<TaskDetails>() {
-            public int compare(TaskDetails task1, TaskDetails task2) {
-                int diff = STRING_COMPARATOR.compare(task1.getPath().getName(), task2.getPath().getName());
-                if (diff != 0) {
-                    return diff;
-                }
-                Path parent1 = task1.getPath().getParent();
-                Path parent2 = task2.getPath().getParent();
-                if (parent1 == null && parent2 != null) {
-                    return -1;
-                }
-                if (parent1 != null && parent2 == null) {
-                    return 1;
-                }
-                if (parent1 == null) {
-                    return 0;
-                }
-                return parent1.compareTo(parent2);
-            }
-        };
-        groups = TreeMultimap.create(keyComparator, taskComparator);
-        for (String group : model.getGroups()) {
-            groups.putAll(group, model.getTasksForGroup(group));
-        }
-        String otherGroupName = findOtherGroup(groups.keySet());
-        if (otherGroupName != null && groups.keySet().contains(TaskReportModel.DEFAULT_GROUP)) {
-            groups.putAll(otherGroupName, groups.removeAll(TaskReportModel.DEFAULT_GROUP));
-        }
-        if (groups.keySet().contains(TaskReportModel.DEFAULT_GROUP) && groups.keySet().size() > 1) {
-            groups.putAll(OTHER_GROUP, groups.removeAll(TaskReportModel.DEFAULT_GROUP));
-        }
-    }
-
-    private String findOtherGroup(Set<String> groupNames) {
-        for (String groupName : groupNames) {
-            if (groupName.equalsIgnoreCase(OTHER_GROUP)) {
-                return groupName;
-            }
-        }
-        return null;
-    }
-
-    public Set<String> getGroups() {
-        return groups.keySet();
-    }
-
-    public Set<TaskDetails> getTasksForGroup(String group) {
-        return groups.get(group);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
deleted file mode 100644
index f3b56c3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
+++ /dev/null
@@ -1,47 +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.tasks.diagnostics.internal;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-
-import java.io.IOException;
-
-/**
- * A {@code DependencyReportRenderer} is responsible for rendering the model of a project dependency report.
- *
- * @author Phil Messenger
- */
-public interface DependencyReportRenderer extends ReportRenderer {
-    /**
-     * Starts rendering the given configuration.
-     * @param configuration The configuration.
-     */
-    void startConfiguration(Configuration configuration);
-
-    /**
-     * Writes the given dependency graph for the current configuration.
-     *
-     * @param resolvedConfiguration The resolved configuration.
-     */
-    void render(ResolvedConfiguration resolvedConfiguration) throws IOException;
-
-    /**
-     * Completes the rendering of the given configuration.
-     * @param configuration The configuration
-     */
-    void completeConfiguration(Configuration configuration);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java
deleted file mode 100644
index 406c999..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.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.api.tasks.diagnostics.internal;
-
-import org.gradle.api.Action;
-import org.gradle.logging.StyledTextOutput;
-
-import static org.gradle.logging.StyledTextOutput.Style.Info;
-
-public class GraphRenderer {
-    private final StyledTextOutput output;
-    private StringBuilder prefix = new StringBuilder();
-    private boolean seenRootChildren;
-    private boolean lastChild = true;
-
-    public GraphRenderer(StyledTextOutput output) {
-        this.output = output;
-    }
-
-    /**
-     * Visits a node in the graph.
-     */
-    public void visit(Action<? super StyledTextOutput> node, boolean lastChild) {
-        if (seenRootChildren) {
-            output.withStyle(Info).text(prefix + (lastChild ? "\\--- " : "+--- "));
-        }
-        this.lastChild = lastChild;
-        node.execute(output);
-        output.println();
-    }
-
-    /**
-     * Starts visiting the children of the most recently visited node.
-     */
-    public void startChildren() {
-        if (seenRootChildren) {
-            prefix.append(lastChild ? "     " : "|    ");
-        }
-        seenRootChildren = true;
-    }
-
-    /**
-     * Completes visiting the children of the node which most recently started visiting children.
-     */
-    public void completeChildren() {
-        if (prefix.length() == 0) {
-            seenRootChildren = false;
-        } else {
-            prefix.setLength(prefix.length() - 5);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphvizReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphvizReportRenderer.java
deleted file mode 100644
index 9653fb8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphvizReportRenderer.java
+++ /dev/null
@@ -1,75 +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.tasks.diagnostics.internal;
-
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.artifacts.ResolvedDependency;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * DependencyGraphRenderer that emits simple graphviz dot notation for a dependency tree.
- *
- * @author Phil Messenger
- */
-public class GraphvizReportRenderer extends TextReportRenderer implements DependencyReportRenderer {
-    @Override
-    public void startProject(Project project) {
-        // Do nothing
-    }
-
-    public void startConfiguration(Configuration configuration) {
-        // Do nothing
-    }
-
-    public void completeConfiguration(Configuration configuration) {
-        // Do nothing
-    }
-
-    public void render(ResolvedConfiguration resolvedConfiguration) throws IOException {
-        getTextOutput().println("digraph SomeConf{");
-
-        Set<String> edges = new HashSet<String>();
-
-        for (ResolvedDependency resolvedDependency : resolvedConfiguration.getFirstLevelModuleDependencies()) {
-            buildDotDependencyTree(resolvedDependency, edges);
-        }
-
-        for (String edge : edges) {
-            getTextOutput().println(edge);
-        }
-
-        getTextOutput().println("}");
-    }
-
-    private void buildDotDependencyTree(ResolvedDependency root, Set<String> edges) {
-        if (root.getAllModuleArtifacts().isEmpty()) {
-            return;
-        }
-        for (ResolvedDependency dep : root.getChildren()) {
-            String edge = "\"" + root.toString() + "\" -> \"" + dep.toString().replace('-', '_') + "\";";
-            edges.add(edge);
-        }
-
-        for (ResolvedDependency dep : root.getChildren()) {
-            buildDotDependencyTree(dep, edges);
-        }
-    }
-}
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
deleted file mode 100644
index 2df766e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
+++ /dev/null
@@ -1,156 +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.diagnostics.internal;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.Project;
-import org.gradle.api.Rule;
-import org.gradle.util.GUtil;
-import org.gradle.util.Path;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import static org.gradle.logging.StyledTextOutput.Style.*;
-
-/**
- * <p>A {@code TaskReportRenderer} is responsible for rendering the model of a project task report.</p>
- *
- * @author Hans Dockter
- */
-public class TaskReportRenderer extends TextReportRenderer {
-    private boolean currentProjectHasTasks;
-    private boolean currentProjectHasRules;
-    private boolean hasContent;
-    private boolean detail;
-
-    @Override
-    public void startProject(Project project) {
-        currentProjectHasTasks = false;
-        currentProjectHasRules = false;
-        hasContent = false;
-        detail = false;
-        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;
-    }
-    
-    /**
-     * Writes the default task names for the current project.
-     *
-     * @param defaultTaskNames The default task names (must not be null)
-     */
-    public void addDefaultTasks(List<String> defaultTaskNames) {
-        if (defaultTaskNames.size() > 0) {
-            getTextOutput().formatln("Default tasks: %s", GUtil.join(defaultTaskNames, ", "));
-            hasContent = true;
-        }
-    }
-
-    public void startTaskGroup(String taskGroup) {
-        if (!GUtil.isTrue(taskGroup)) {
-            addSubheading("Tasks");
-        } else {
-            addSubheading(StringUtils.capitalize(taskGroup) + " tasks");
-        }
-        currentProjectHasTasks = true;
-    }
-
-    /**
-     * Writes a task for the current project.
-     *
-     * @param task The task
-     */
-    public void addTask(TaskDetails task) {
-        writeTask(task, "");
-    }
-
-    public void addChildTask(TaskDetails task) {
-        if (detail) {
-            writeTask(task, "    ");
-        }
-    }
-
-    private void writeTask(TaskDetails task, String prefix) {
-        getTextOutput().text(prefix);
-        getTextOutput().withStyle(Identifier).text(task.getPath());
-        if (GUtil.isTrue(task.getDescription())) {
-            getTextOutput().withStyle(Description).format(" - %s", task.getDescription());
-        }
-        if (detail) {
-            SortedSet<Path> sortedDependencies = new TreeSet<Path>();
-            for (TaskDetails dependency : task.getDependencies()) {
-                sortedDependencies.add(dependency.getPath());
-            }
-            if (sortedDependencies.size() > 0) {
-                getTextOutput().withStyle(Info).format(" [%s]", GUtil.join(sortedDependencies, ", "));
-            }
-        }
-        getTextOutput().println();
-    }
-
-    private void addSubheading(String header) {
-        if (hasContent) {
-            getTextOutput().println();
-        }
-        hasContent = true;
-        writeSubheading(header);
-    }
-
-    /**
-     * Marks the end of the tasks for the current project.
-     */
-    public void completeTasks() {
-        if (!currentProjectHasTasks) {
-            getTextOutput().withStyle(Info).println("No tasks");
-            hasContent = true;
-        }
-    }
-
-    /**
-     * Writes a rule for the current project.
-     *
-     * @param rule The rule
-     */
-    public void addRule(Rule rule) {
-        if (!currentProjectHasRules) {
-            addSubheading("Rules");
-        }
-        getTextOutput().println(GUtil.elvis(rule.getDescription(), ""));
-        currentProjectHasRules = true;
-    }
-
-    @Override
-    public void complete() throws IOException {
-        if (!detail) {
-            getTextOutput().println();
-            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/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java
deleted file mode 100644
index 3df8c89..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-/**
- * The built-in diagnostic {@link org.gradle.api.Task} implementations.
- */
-package org.gradle.api.tasks.diagnostics;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy
deleted file mode 100644
index 05fd1e4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy
+++ /dev/null
@@ -1,248 +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.util
-
-import org.gradle.api.tasks.AntBuilderAware
-import org.gradle.util.GUtil
-
-import org.gradle.api.specs.Spec
-import org.gradle.api.specs.Specs
-import org.gradle.api.internal.file.pattern.PatternMatcherFactory
-import org.gradle.api.specs.AndSpec
-import org.gradle.api.specs.NotSpec
-import org.gradle.api.specs.OrSpec
-import org.apache.tools.ant.DirectoryScanner
-import org.gradle.api.file.FileTreeElement
-import org.gradle.api.internal.file.RelativePathSpec
-
-/**
- * @author Hans Dockter
- */
-class PatternSet implements AntBuilderAware, PatternFilterable {
-    PatternSet() {
-    }
-
-    PatternSet(Map args) {
-        args.each {String key, value ->
-            this."$key" = value
-        }
-    }
-
-    private static final Set<String> GLOBAL_EXCLUDES = new HashSet<String>()
-
-    private Set includes = [] as LinkedHashSet
-    private Set excludes = [] as LinkedHashSet
-    private Set includeSpecs = [] as LinkedHashSet
-    private Set excludeSpecs = [] as LinkedHashSet
-    def boolean caseSensitive = true
-
-    static {
-        resetGlobalExcludes()
-    }
-
-    static def setGlobalExcludes(Collection<String> excludes) {
-        GLOBAL_EXCLUDES.clear()
-        GLOBAL_EXCLUDES.addAll(excludes)
-    }
-
-    static def resetGlobalExcludes() {
-        setGlobalExcludes(DirectoryScanner.DEFAULTEXCLUDES as Collection)
-    }
-
-    def boolean equals(Object o) {
-        if (o.is(this)) {
-            return true
-        }
-        if (o == null || o.getClass() != getClass()) {
-            return false
-        }
-        return o.includes == includes && o.excludes == excludes && o.caseSensitive == caseSensitive
-    }
-
-    def int hashCode() {
-        return includes.hashCode() ^ excludes.hashCode()
-    }
-
-    public PatternSet copyFrom(PatternFilterable sourcePattern) {
-        setIncludes(sourcePattern.includes)
-        setExcludes(sourcePattern.excludes)
-        PatternSet other = sourcePattern
-        includeSpecs.clear()
-        includeSpecs.addAll(other.includeSpecs)
-        excludeSpecs.clear()
-        excludeSpecs.addAll(other.excludeSpecs)
-        this
-    }
-
-    public PatternSet intersect() {
-        return new IntersectionPatternSet(this)
-    }
-
-    public Spec<FileTreeElement> getAsSpec() {
-        Spec<FileTreeElement> includeSpec = Specs.satisfyAll()
-
-        boolean hasIncludes = includes || includeSpecs
-        if (hasIncludes) {
-            List<Spec<FileTreeElement>> matchers = []
-            for (String include: includes) {
-                matchers.add(new RelativePathSpec(PatternMatcherFactory.getPatternMatcher(true, caseSensitive, include)))
-            }
-            matchers.addAll(includeSpecs)
-            includeSpec = new OrSpec<FileTreeElement>(matchers as Spec[])
-        }
-
-        Collection<String> allExcludes = excludes + GLOBAL_EXCLUDES
-        boolean hasExcludes = allExcludes || excludeSpecs
-        if (!hasExcludes) {
-            return includeSpec
-        }
-
-        List<Spec<FileTreeElement>> matchers = []
-        for (String exclude: allExcludes) {
-            matchers.add(new RelativePathSpec(PatternMatcherFactory.getPatternMatcher(false, caseSensitive, exclude)))
-        }
-        matchers.addAll(excludeSpecs)
-        Spec<FileTreeElement> excludeSpec = new NotSpec<FileTreeElement>(new OrSpec<FileTreeElement>(matchers as Spec[]))
-
-        if (!hasIncludes) {
-            return excludeSpec
-        }
-
-        return new AndSpec<FileTreeElement>([includeSpec, excludeSpec] as Spec[])
-    }
-
-    public Set<String> getIncludes() {
-        includes
-    }
-
-    public Set<Spec<FileTreeElement>> getIncludeSpecs() {
-        return includeSpecs
-    }
-
-    public PatternSet setIncludes(Iterable<String> includes) {
-        this.includes.clear()
-        include(includes)
-    }
-
-    public Set<String> getExcludes() {
-        excludes
-    }
-
-    public Set<Spec<FileTreeElement>> getExcludeSpecs() {
-        return excludeSpecs
-    }
-
-    public PatternSet setExcludes(Iterable<String> excludes) {
-        this.excludes.clear()
-        exclude(excludes)
-    }
-
-    public PatternFilterable include(String... includes) {
-        include(includes as List)
-    }
-
-    public PatternFilterable include(Iterable<String> includes) {
-        GUtil.addToCollection(this.includes, includes)
-        this
-    }
-
-    public PatternFilterable include(Spec<FileTreeElement> spec) {
-        includeSpecs << spec
-        this
-    }
-
-    /*
-    This can't be called just include, because it has the same erasure as include(Iterable<String>)
-     */
-
-    public PatternFilterable includeSpecs(Iterable<Spec<FileTreeElement>> includes) {
-        GUtil.addToCollection(this.includeSpecs, includes)
-        this
-    }
-
-    public PatternFilterable include(Closure closure) {
-        include(closure as Spec)
-        this
-    }
-
-    public PatternFilterable exclude(String... excludes) {
-        exclude(excludes as List)
-    }
-
-    public PatternFilterable exclude(Iterable<String> excludes) {
-        GUtil.addToCollection(this.excludes, excludes)
-        this
-    }
-
-    public PatternFilterable exclude(Spec<FileTreeElement> spec) {
-        excludeSpecs << spec
-        this
-    }
-
-    public PatternFilterable excludeSpecs(Iterable<Spec<FileTreeElement>> excludes) {
-        GUtil.addToCollection(this.excludeSpecs, excludes)
-        this
-    }
-
-    public PatternFilterable exclude(Closure closure) {
-        exclude(closure as Spec)
-        this
-    }
-
-    def addToAntBuilder(node, String childNodeName = null) {
-        if (!includeSpecs.empty || !excludeSpecs.empty) {
-            throw new UnsupportedOperationException('Cannot add include/exclude specs to Ant node. Only include/exclude patterns are currently supported.')
-        }
-        node.and {
-            if (includes) {
-                or {
-                    includes.each {
-                        filename(name: it, casesensitive: this.caseSensitive)
-                    }
-                }
-            }
-            if (excludes) {
-                not {
-                    or {
-                        excludes.each {
-                            filename(name: it, casesensitive: this.caseSensitive)
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-class IntersectionPatternSet extends PatternSet {
-    private final PatternSet other
-
-    def IntersectionPatternSet(PatternSet other) {
-        this.other = other
-    }
-
-    def Spec<FileTreeElement> getAsSpec() {
-        return new AndSpec<FileTreeElement>([super.getAsSpec(), other.getAsSpec()] as Spec[])
-    }
-
-    def addToAntBuilder(Object node, String childNodeName) {
-        node.and {
-            super.addToAntBuilder(node, null)
-            other.addToAntBuilder(node, null)
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java
new file mode 100644
index 0000000..1c1fcfa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.java
@@ -0,0 +1,250 @@
+/*
+ * 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.util;
+
+import groovy.lang.Closure;
+import org.apache.tools.ant.DirectoryScanner;
+import org.gradle.api.Action;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.RelativePathSpec;
+import org.gradle.api.internal.file.pattern.PatternMatcherFactory;
+import org.gradle.api.specs.*;
+import org.gradle.api.tasks.AntBuilderAware;
+import org.gradle.api.tasks.util.internal.PatternSetAntBuilderDelegate;
+import org.gradle.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * {@inheritDoc}
+ */
+public class PatternSet implements AntBuilderAware, PatternFilterable {
+
+    private Set<String> includes = new LinkedHashSet<String>();
+    private Set<String> excludes = new LinkedHashSet<String>();
+    private Set<Spec<FileTreeElement>> includeSpecs = new LinkedHashSet<Spec<FileTreeElement>>();
+    private Set<Spec<FileTreeElement>> excludeSpecs = new LinkedHashSet<Spec<FileTreeElement>>();
+
+    boolean caseSensitive = true;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PatternSet that = (PatternSet) o;
+
+        if (caseSensitive != that.caseSensitive) {
+            return false;
+        }
+        if (!excludes.equals(that.excludes)) {
+            return false;
+        }
+        if (!includes.equals(that.includes)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = includes.hashCode();
+        result = 31 * result + excludes.hashCode();
+        result = 31 * result + (caseSensitive ? 1 : 0);
+        return result;
+    }
+
+    public PatternSet copyFrom(PatternFilterable sourcePattern) {
+        setIncludes(sourcePattern.getIncludes());
+        setExcludes(sourcePattern.getExcludes());
+        PatternSet other = (PatternSet) sourcePattern;
+        includeSpecs.clear();
+        includeSpecs.addAll(other.includeSpecs);
+        excludeSpecs.clear();
+        excludeSpecs.addAll(other.excludeSpecs);
+
+        return this;
+    }
+
+    public PatternSet intersect() {
+        return new IntersectionPatternSet(this);
+    }
+
+    private static class IntersectionPatternSet extends PatternSet {
+
+        private final PatternSet other;
+
+        public IntersectionPatternSet(PatternSet other) {
+            this.other = other;
+        }
+
+        public Spec<FileTreeElement> getAsSpec() {
+            return new AndSpec<FileTreeElement>(super.getAsSpec(), other.getAsSpec());
+        }
+
+        public Object addToAntBuilder(Object node, String childNodeName) {
+            return PatternSetAntBuilderDelegate.and(node, new Action<Object>() {
+                public void execute(Object andNode) {
+                    IntersectionPatternSet.super.addToAntBuilder(andNode, null);
+                    other.addToAntBuilder(andNode, null);
+                }
+            });
+        }
+    }
+
+    public Spec<FileTreeElement> getAsSpec() {
+        Spec<FileTreeElement> includeSpec = Specs.satisfyAll();
+
+        Set<String> includes = this.getIncludes();
+        Set<Spec<FileTreeElement>> includeSpecs = getIncludeSpecs();
+
+        boolean hasIncludes = !includes.isEmpty() || !includeSpecs.isEmpty();
+        if (hasIncludes) {
+            List<Spec<FileTreeElement>> matchers = new LinkedList<Spec<FileTreeElement>>();
+            for (String include : includes) {
+                Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(true, isCaseSensitive(), include);
+                matchers.add(new RelativePathSpec(patternMatcher));
+            }
+            matchers.addAll(includeSpecs);
+            includeSpec = new OrSpec<FileTreeElement>(matchers);
+        }
+
+
+        Set<String> excludes = getExcludes();
+        Collection<String> allExcludes = new HashSet<String>(excludes);
+        Collections.addAll(allExcludes, DirectoryScanner.getDefaultExcludes());
+
+        Set<Spec<FileTreeElement>> excludeSpecs = getExcludeSpecs();
+
+        List<Spec<FileTreeElement>> matchers = new LinkedList<Spec<FileTreeElement>>();
+        for (String exclude : allExcludes) {
+            Spec<RelativePath> patternMatcher = PatternMatcherFactory.getPatternMatcher(false, isCaseSensitive(), exclude);
+            matchers.add(new RelativePathSpec(patternMatcher));
+        }
+
+        matchers.addAll(excludeSpecs);
+        Spec<FileTreeElement> excludeSpec = new NotSpec<FileTreeElement>(new OrSpec<FileTreeElement>(matchers));
+
+        if (!hasIncludes) {
+            return excludeSpec;
+        }
+
+        return new AndSpec<FileTreeElement>(includeSpec, excludeSpec);
+    }
+
+    public Set<String> getIncludes() {
+        return includes;
+    }
+
+    public Set<Spec<FileTreeElement>> getIncludeSpecs() {
+        return includeSpecs;
+    }
+
+    public PatternSet setIncludes(Iterable<String> includes) {
+        this.includes.clear();
+        return include(includes);
+    }
+    public PatternSet include(String... includes) {
+        return include(Arrays.asList(includes));
+    }
+
+    public PatternSet include(Iterable<String> includes) {
+        CollectionUtils.addAll(this.includes, includes);
+        return this;
+    }
+
+    public PatternSet include(Spec<FileTreeElement> spec) {
+        includeSpecs.add(spec);
+        return this;
+    }
+
+    public Set<String> getExcludes() {
+        return excludes;
+    }
+
+    public Set<Spec<FileTreeElement>> getExcludeSpecs() {
+        return excludeSpecs;
+    }
+
+    public PatternSet setExcludes(Iterable<String> excludes) {
+        this.excludes.clear();
+        return exclude(excludes);
+    }
+
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    /*
+    This can't be called just include, because it has the same erasure as include(Iterable<String>)
+     */
+
+    public PatternSet includeSpecs(Iterable<Spec<FileTreeElement>> includeSpecs) {
+        CollectionUtils.addAll(this.includeSpecs, includeSpecs);
+        return this;
+    }
+
+    public PatternSet include(Closure closure) {
+        include(Specs.<FileTreeElement>convertClosureToSpec(closure));
+        return this;
+    }
+
+    public PatternSet exclude(String... excludes) {
+        Collections.addAll(this.excludes, excludes);
+        return this;
+    }
+
+    public PatternSet exclude(Iterable<String> excludes) {
+        CollectionUtils.addAll(this.excludes, excludes);
+        return this;
+    }
+
+    public PatternSet exclude(Spec<FileTreeElement> spec) {
+        excludeSpecs.add(spec);
+        return this;
+    }
+
+    public PatternSet excludeSpecs(Iterable<Spec<FileTreeElement>> excludes) {
+        CollectionUtils.addAll(this.excludeSpecs, excludes);
+        return this;
+    }
+
+    public PatternSet exclude(Closure closure) {
+        exclude(Specs.<FileTreeElement>convertClosureToSpec(closure));
+        return this;
+    }
+
+    public Object addToAntBuilder(Object node, String childNodeName) {
+
+        if (!getIncludeSpecs().isEmpty() || !getExcludeSpecs().isEmpty()) {
+            throw new UnsupportedOperationException("Cannot add include/exclude specs to Ant node. Only include/exclude patterns are currently supported.");
+        }
+
+        return new PatternSetAntBuilderDelegate(getIncludes(), getExcludes(), isCaseSensitive()).addToAntBuilder(node, childNodeName);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/internal/PatternSetAntBuilderDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/internal/PatternSetAntBuilderDelegate.java
new file mode 100644
index 0000000..ef4efb7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/internal/PatternSetAntBuilderDelegate.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.tasks.util.internal;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObject;
+import org.gradle.api.Action;
+import org.gradle.api.tasks.AntBuilderAware;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Externalised from PatternSet to isolate the Groovy usage.
+ */
+public class PatternSetAntBuilderDelegate implements AntBuilderAware {
+
+    private final Set<String> includes;
+    private final Set<String> excludes;
+    private final boolean caseSensitive;
+
+    public PatternSetAntBuilderDelegate(Set<String> includes, Set<String> excludes, boolean caseSensitive) {
+        this.includes = includes;
+        this.excludes = excludes;
+        this.caseSensitive = caseSensitive;
+    }
+
+    private static Object logical(Object node, String op, final Action<Object> withNode) {
+        GroovyObject groovyObject = (GroovyObject) node;
+        groovyObject.invokeMethod(op, new Closure(null, null) {
+            void doCall() {
+                withNode.execute(getDelegate());
+            }
+        });
+        return node;
+    }
+
+    public static Object and(Object node, Action<Object> withNode) {
+        return logical(node, "and", withNode);
+    }
+
+    private static Object or(Object node, Action<Object> withNode) {
+        return logical(node, "or", withNode);
+    }
+
+    private static Object not(Object node, Action<Object> withNode) {
+        return logical(node, "not", withNode);
+    }
+
+    private static Object addFilenames(Object node, Iterable<String> filenames, boolean caseSensitive) {
+        GroovyObject groovyObject = (GroovyObject) node;
+        Map<String, Object> props = new HashMap<String, Object>(2);
+        props.put("casesensitive", caseSensitive);
+        for (String filename : filenames) {
+            props.put("name", filename);
+            groovyObject.invokeMethod("filename", props);
+        }
+        return node;
+    }
+
+    public Object addToAntBuilder(Object node, String childNodeName) {
+        return and(node, new Action<Object>() {
+            public void execute(Object node) {
+                if (!includes.isEmpty()) {
+                    or(node, new Action<Object>() {
+                        public void execute(Object node) {
+                            addFilenames(node, includes, caseSensitive);
+                        }
+                    });
+                }
+
+                if (!excludes.isEmpty()) {
+                    not(node, new Action<Object>() {
+                        public void execute(Object node) {
+                            or(node, new Action<Object>() {
+                                public void execute(Object node) {
+                                    addFilenames(node, excludes, caseSensitive);
+                                }
+                            });
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+}
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
index f57a7f6..1acd5f3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
@@ -16,13 +16,14 @@
 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.messaging.serialize.Serializer;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.internal.Factories;
+import org.gradle.internal.Factory;
 import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -47,8 +48,12 @@ public class DefaultCacheAccess implements CacheAccess {
     private Thread owner;
     private FileLockManager.LockMode lockMode;
     private FileLock fileLock;
-    private boolean started;
-    private final List<String> operationStack = new ArrayList<String>();
+    private final ThreadLocal<CacheOperationStack> operationStack = new ThreadLocal<CacheOperationStack>() {
+        @Override
+        protected CacheOperationStack initialValue() {
+            return new CacheOperationStack();
+        }
+    };
 
     public DefaultCacheAccess(String cacheDisplayName, File lockFile, FileLockManager lockManager) {
         this.cacheDiplayName = cacheDisplayName;
@@ -71,9 +76,8 @@ public class DefaultCacheAccess implements CacheAccess {
             if (lockMode == FileLockManager.LockMode.None) {
                 return;
             }
-            started = true;
             fileLock = lockManager.lock(lockFile, lockMode, cacheDiplayName);
-            lockCache(String.format("Access %s", cacheDiplayName));
+            takeOwnership(String.format("Access %s", cacheDiplayName));
         } finally {
             lock.unlock();
         }
@@ -85,8 +89,7 @@ public class DefaultCacheAccess implements CacheAccess {
             for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
                 cache.close();
             }
-            operationStack.clear();
-            started = false;
+            operationStack.remove();
             lockMode = null;
             owner = null;
             if (fileLock != null) {
@@ -105,36 +108,31 @@ public class DefaultCacheAccess implements CacheAccess {
         return fileLock;
     }
 
-    public void useCache(String operationDisplayName, final Runnable action) {
-        useCache(operationDisplayName, new Factory<Object>() {
-            public Object create() {
-                action.run();
-                return null;
-            }
-        });
+    public void useCache(String operationDisplayName, Runnable action) {
+        useCache(operationDisplayName, Factories.toFactory(action));
     }
 
-    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> factory) {
         if (lockMode == FileLockManager.LockMode.Shared) {
             throw new UnsupportedOperationException("Not implemented yet.");
         }
 
-        lockCache(operationDisplayName);
+        takeOwnership(operationDisplayName);
         try {
             boolean wasStarted = onStartWork();
             try {
-                return action.create();
+                return factory.create();
             } finally {
                 if (wasStarted) {
                     onEndWork();
                 }
             }
         } finally {
-            unlockCache();
+            releaseOwnership(operationDisplayName);
         }
     }
 
-    private void lockCache(String operationDisplayName) {
+    private void takeOwnership(String operationDisplayName) {
         lock.lock();
         try {
             while (owner != null && owner != Thread.currentThread()) {
@@ -145,17 +143,17 @@ public class DefaultCacheAccess implements CacheAccess {
                 }
             }
             owner = Thread.currentThread();
-            operationStack.add(0, operationDisplayName);
+            operationStack.get().pushCacheAction(operationDisplayName);
         } finally {
             lock.unlock();
         }
     }
 
-    private void unlockCache() {
+    private void releaseOwnership(String operationDisplayName) {
         lock.lock();
         try {
-            operationStack.remove(0);
-            if (operationStack.isEmpty()) {
+            operationStack.get().popCacheAction(operationDisplayName);
+            if (!operationStack.get().isInCacheAction()) {
                 owner = null;
                 condition.signalAll();
             }
@@ -165,22 +163,29 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
-        startLongRunningOperation();
-        try {
-            boolean wasEnded = onEndWork();
+        if (operationStack.get().isInLongRunningOperation()) {
+            operationStack.get().pushLongRunningOperation(operationDisplayName);
             try {
                 return action.create();
             } finally {
-                if (wasEnded) {
-                    onStartWork();
-                }
+                operationStack.get().popLongRunningOperation(operationDisplayName);
             }
+        }
+
+        checkThreadIsOwner();
+        boolean wasEnded = onEndWork();
+        parkOwner(operationDisplayName);
+        try {
+            return action.create();
         } finally {
-            endLongRunningOperation();
+            restoreOwner(operationDisplayName);
+            if (wasEnded) {
+                onStartWork();
+            }
         }
     }
 
-    private void startLongRunningOperation() {
+    private void checkThreadIsOwner() {
         lock.lock();
         try {
             if (owner != Thread.currentThread()) {
@@ -191,16 +196,40 @@ public class DefaultCacheAccess implements CacheAccess {
         }
     }
 
-    private void endLongRunningOperation() {
+    private void parkOwner(String operationDisplayName) {
+        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));
+            }
+            owner = null;
+            condition.signalAll();
+
+            operationStack.get().pushLongRunningOperation(operationDisplayName);
+        } finally {
+            lock.unlock();
+        }
     }
 
-    public void longRunningOperation(String operationDisplayName, final Runnable action) {
-        longRunningOperation(operationDisplayName, new Factory<Object>() {
-            public Object create() {
-                action.run();
-                return null;
+    private void restoreOwner(String description) {
+        lock.lock();
+        try {
+            while (owner != null) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
             }
-        });
+            owner = Thread.currentThread();
+            operationStack.get().popLongRunningOperation(description);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void longRunningOperation(String operationDisplayName, Runnable action) {
+        longRunningOperation(operationDisplayName, Factories.toFactory(action));
     }
 
     public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Class<V> valueType) {
@@ -217,8 +246,8 @@ public class DefaultCacheAccess implements CacheAccess {
         lock.lock();
         try {
             caches.add(indexedCache);
-            if (started) {
-                indexedCache.onStartWork(operationStack.get(0));
+            if (fileLock != null) {
+                indexedCache.onStartWork(operationStack.get().getDescription());
             }
         } finally {
             lock.unlock();
@@ -231,19 +260,19 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     private boolean onStartWork() {
-        if (started) {
+        if (fileLock != null) {
             return false;
         }
 
-        started = true;
+        fileLock = lockManager.lock(lockFile, Exclusive, cacheDiplayName, operationStack.get().getDescription());
         for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
-            cache.onStartWork(operationStack.get(0));
+            cache.onStartWork(operationStack.get().getDescription());
         }
         return true;
     }
 
     private boolean onEndWork() {
-        if (!started) {
+        if (fileLock == null) {
             return false;
         }
 
@@ -251,11 +280,8 @@ public class DefaultCacheAccess implements CacheAccess {
             for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
                 cache.onEndWork();
             }
-            if (fileLock != null) {
-                fileLock.close();
-            }
+            fileLock.close();
         } finally {
-            started = false;
             fileLock = null;
         }
         return true;
@@ -264,15 +290,12 @@ public class DefaultCacheAccess implements CacheAccess {
     private FileLock getLock() {
         lock.lock();
         try {
-            if (Thread.currentThread() != owner || !started) {
+            if (Thread.currentThread() != owner || fileLock == null) {
                 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;
     }
 
@@ -290,4 +313,61 @@ public class DefaultCacheAccess implements CacheAccess {
         }
     }
 
+    private class CacheOperationStack {
+        private final List<CacheOperation> operations = new ArrayList<CacheOperation>();
+
+        public String getDescription() {
+            checkNotEmpty();
+            return operations.get(0).description;
+        }
+
+        public boolean isInLongRunningOperation() {
+            return !operations.isEmpty() && operations.get(0).longRunningOperation;
+        }
+
+        public void pushLongRunningOperation(String description) {
+            operations.add(0, new CacheOperation(description, true));
+        }
+
+        public void popLongRunningOperation(String description) {
+            pop(description, true);
+        }
+
+        public boolean isInCacheAction() {
+            return !operations.isEmpty() && !operations.get(0).longRunningOperation;
+        }
+
+        public void pushCacheAction(String description) {
+            operations.add(0, new CacheOperation(description, false));
+        }
+
+        public void popCacheAction(String description) {
+            pop(description, false);
+        }
+
+        private CacheOperation pop(String description, boolean longRunningOperation) {
+            checkNotEmpty();
+            CacheOperation operation = operations.remove(0);
+            if (operation.description.equals(description) && operation.longRunningOperation == longRunningOperation) {
+                return operation;
+            }
+            throw new IllegalStateException();
+        }
+
+        private void checkNotEmpty() {
+            if (operations.isEmpty()) {
+                throw new IllegalStateException();
+            }
+        }
+    }
+
+    private class CacheOperation {
+        final String description;
+        final boolean longRunningOperation;
+
+        private CacheOperation(String description, boolean longRunningOperation) {
+            this.description = description;
+            this.longRunningOperation = longRunningOperation;
+        }
+    }
 }
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
index 0a0f182..4c4097b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
@@ -33,7 +33,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
  */
 public class DefaultFileLockManager implements FileLockManager {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileLockManager.class);
-    private static final int LOCK_TIMEOUT = 60000;
+    private static final int DEFAULT_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;
@@ -43,9 +43,15 @@ public class DefaultFileLockManager implements FileLockManager {
     public static final int INFORMATION_REGION_DESCR_CHUNK_LIMIT = 340;
     private final Set<File> lockedFiles = new CopyOnWriteArraySet<File>();
     private final ProcessMetaDataProvider metaDataProvider;
+    private final int lockTimeoutMs;
 
     public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider) {
+        this(metaDataProvider, DEFAULT_LOCK_TIMEOUT);
+    }
+
+    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider, int lockTimeoutMs) {
         this.metaDataProvider = metaDataProvider;
+        this.lockTimeoutMs = lockTimeoutMs;
     }
 
     public FileLock lock(File target, LockMode mode, String targetDisplayName) throws LockTimeoutException {
@@ -218,7 +224,7 @@ public class DefaultFileLockManager implements FileLockManager {
 
         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;
+            long timeout = System.currentTimeMillis() + lockTimeoutMs;
 
             // Lock the state region, with the requested mode
             java.nio.channels.FileLock stateRegionLock = lockStateRegion(lockMode, timeout);
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
index 7c83272..4f0347a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
@@ -19,6 +19,7 @@ import org.gradle.api.Action;
 import org.gradle.cache.*;
 import org.gradle.internal.Factory;
 import org.gradle.messaging.serialize.Serializer;
+import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -38,7 +39,7 @@ public class DefaultPersistentDirectoryStore implements ReferencablePersistentCa
     }
 
     public DefaultPersistentDirectoryStore open() {
-        dir.mkdirs();
+        GFileUtils.createDirectory(dir);
         cacheAccess = createCacheAccess();
         try {
             cacheAccess.open(lockMode);
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
index 38b8acf..271ce4a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
@@ -88,7 +88,7 @@ public class DelegateOnDemandPersistentDirectoryCache implements ReferencablePer
                 delegateCache.close();
             }
         } else {
-            throw new CacheOpenException("Cannot run operation on not opened cache");
+            throw new CacheOpenException("Cannot run operation on cache that has not been opened.");
         }
     }
 
@@ -105,6 +105,6 @@ public class DelegateOnDemandPersistentDirectoryCache implements ReferencablePer
     }
 
     public String toString(){
-        return String.format("Delegate On Demand Cache for %s", delegateCache.toString());
+        return String.format("On Demand Cache for %s", delegateCache.toString());
     }
 }
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
index 0a542bd..7b155c9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java
@@ -15,8 +15,17 @@
  */
 package org.gradle.cache.internal;
 
+/**
+ * Participates in a unit of work that accesses the cache. Implementations do not need to be thread-safe and are accessed by a single thread at a time.
+ */
 public interface UnitOfWorkParticipant {
+    /**
+     * Called just after the cache is locked. Called before any work has been performed.
+     */
     void onStartWork(String operationDisplayName);
 
+    /**
+     * Called just before the cache is to be unlocked. Called after all work has been completed.
+     */
     void onEndWork();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
index bed7142..51f2aff 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
@@ -17,27 +17,18 @@ package org.gradle.configuration;
 
 import org.gradle.api.Action;
 import org.gradle.api.Project;
+import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 public class DefaultBuildConfigurer implements BuildConfigurer {
-    private List<Action<? super ProjectInternal>> actions;
+    private Action<Project> actions;
 
     public DefaultBuildConfigurer(Action<? super ProjectInternal>... actions) {
-        this.actions = new ArrayList<Action<? super ProjectInternal>>(Arrays.asList(actions));
+        this.actions = Actions.castBefore(ProjectInternal.class, Actions.composite(actions));
     }
 
     public void configure(GradleInternal gradle) {
-        gradle.getRootProject().allprojects(new Action<Project>() {
-            public void execute(Project project) {
-                for (Action<? super ProjectInternal> action : actions) {
-                    action.execute((ProjectInternal) project);
-                }
-            }
-        });
+        gradle.getRootProject().allprojects(actions);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/Help.java b/subprojects/core/src/main/groovy/org/gradle/configuration/Help.java
deleted file mode 100644
index 362c725..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/Help.java
+++ /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.configuration;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.logging.StyledTextOutput;
-import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.util.GradleVersion;
-
-import static org.gradle.logging.StyledTextOutput.Style.*;
-
-public class Help extends DefaultTask {
-    @TaskAction
-    void displayHelp() {
-        StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(Help.class);
-        BuildClientMetaData metaData = getServices().get(BuildClientMetaData.class);
-
-        output.println();
-        output.formatln("Welcome to Gradle %s.", GradleVersion.current().getVersion());
-        output.println();
-        output.text("To run a build, run ");
-        metaData.describeCommand(output.withStyle(UserInput), "<task> ...");
-        output.println();
-        output.println();
-        output.text("To see a list of available tasks, run ");
-        metaData.describeCommand(output.withStyle(UserInput), "tasks");
-        output.println();
-        output.println();
-        output.text("To see a list of command-line options, run ");
-        metaData.describeCommand(output.withStyle(UserInput), "--help");
-        output.println();
-    }
-}
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 8fda816..1d1d3b2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
@@ -16,43 +16,18 @@
 package org.gradle.configuration;
 
 import org.gradle.api.Action;
-import org.gradle.api.Task;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.tasks.diagnostics.DependencyReportTask;
-import org.gradle.api.tasks.diagnostics.ProjectReportTask;
-import org.gradle.api.tasks.diagnostics.PropertyReportTask;
-import org.gradle.api.tasks.diagnostics.TaskReportTask;
 
 public class ImplicitTasksConfigurer implements Action<ProjectInternal> {
-    private static final String HELP_GROUP = "help";
+    public static final String HELP_GROUP = "help";
     public static final String HELP_TASK = "help";
     public static final String PROJECTS_TASK = "projects";
     public static final String TASKS_TASK = "tasks";
-    public static final String DEPENDENCIES_TASK = "dependencies";
     public static final String PROPERTIES_TASK = "properties";
+    public static final String DEPENDENCIES_TASK = "dependencies";
+    public static final String DEPENDENCY_INSIGHT_TASK = "dependencyInsight";
 
     public void execute(ProjectInternal project) {
-        TaskContainerInternal tasks = project.getImplicitTasks();
-
-        Task task = tasks.add(HELP_TASK, Help.class);
-        task.setDescription("Displays a help message");
-        task.setGroup(HELP_GROUP);
-
-        task = tasks.add(PROJECTS_TASK, ProjectReportTask.class);
-        task.setDescription(String.format("Displays the sub-projects of %s.", project));
-        task.setGroup(HELP_GROUP);
-
-        task = tasks.add(TASKS_TASK, TaskReportTask.class);
-        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);
-        task.setDescription(String.format("Displays the dependencies of %s.", project));
-        task.setGroup(HELP_GROUP);
-
-        task = tasks.add(PROPERTIES_TASK, PropertyReportTask.class);
-        task.setDescription(String.format("Displays the properties of %s.", project));
-        task.setGroup(HELP_GROUP);
+        project.getPlugins().apply("help-tasks");
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
deleted file mode 100644
index 32ac012..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
+++ /dev/null
@@ -1,241 +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 groovy.lang.Closure;
-import org.gradle.api.CircularReferenceException;
-import org.gradle.api.Task;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.tasks.CachingTaskDependencyResolveContext;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.listener.ListenerBroadcast;
-import org.gradle.listener.ListenerManager;
-import org.gradle.util.Clock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
-    private static Logger logger = LoggerFactory.getLogger(DefaultTaskGraphExecuter.class);
-
-    private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
-    private final ListenerBroadcast<TaskExecutionListener> taskListeners;
-    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);
-        taskListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionListener.class);
-    }
-
-    public void useFilter(Spec<? super Task> filter) {
-        this.filter = filter;
-    }
-
-    public void addTasks(Iterable<? extends Task> tasks) {
-        assert tasks != null;
-
-        Clock clock = new Clock();
-
-        Set<Task> sortedTasks = new TreeSet<Task>();
-        for (Task task : tasks) {
-            sortedTasks.add(task);
-        }
-        fillDag(sortedTasks);
-        populated = true;
-
-        logger.debug("Timing: Creating the DAG took " + clock.getTime());
-    }
-
-    public void execute() {
-        Clock clock = new Clock();
-
-        graphListeners.getSource().graphPopulated(this);
-
-        try {
-            doExecute(executionPlan.values());
-            logger.debug("Timing: Executing the DAG took " + clock.getTime());
-        } finally {
-            executionPlan.clear();
-        }
-    }
-
-    public void execute(Iterable<? extends Task> tasks) {
-        addTasks(tasks);
-        execute();
-    }
-
-    private void fillDag(Collection<? extends Task> tasks) {
-        Set<Task> visiting = new HashSet<Task>();
-        List<Task> queue = new ArrayList<Task>();
-        queue.addAll(tasks);
-        CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
-
-        while (!queue.isEmpty()) {
-            Task task = queue.get(0);
-            if (!filter.isSatisfiedBy(task)) {
-                // Filtered - skip
-                queue.remove(0);
-                continue;
-            }
-            if (executionPlan.containsKey(task)) {
-                // Already in plan - skip
-                queue.remove(0);
-                continue;
-            }
-
-            if (visiting.add(task)) {
-                // Have not seen this task before - add its dependencies to the head of the queue and leave this
-                // task in the queue
-                Set<Task> dependsOnTasks = new TreeSet<Task>(Collections.reverseOrder());
-                dependsOnTasks.addAll(context.getDependencies(task));
-                for (Task dependsOnTask : dependsOnTasks) {
-                    if (visiting.contains(dependsOnTask)) {
-                        throw new CircularReferenceException(String.format(
-                                "Circular dependency between tasks. Cycle includes [%s, %s].", task, dependsOnTask));
-                    }
-                    queue.add(0, dependsOnTask);
-                }
-            } else {
-                // Have visited this task's dependencies - add it to the end of the plan
-                queue.remove(0);
-                visiting.remove(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));
-            }
-        }
-    }
-
-    public void addTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
-        graphListeners.add(listener);
-    }
-
-    public void removeTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
-        graphListeners.remove(listener);
-    }
-
-    public void whenReady(final Closure closure) {
-        graphListeners.add("graphPopulated", closure);
-    }
-
-    public void addTaskExecutionListener(TaskExecutionListener listener) {
-        taskListeners.add(listener);
-    }
-
-    public void removeTaskExecutionListener(TaskExecutionListener listener) {
-        taskListeners.remove(listener);
-    }
-
-    public void beforeTask(final Closure closure) {
-        taskListeners.add("beforeExecute", closure);
-    }
-
-    public void afterTask(final Closure closure) {
-        taskListeners.add("afterExecute", closure);
-    }
-
-    public void useFailureHandler(TaskFailureHandler handler) {
-        this.failureHandler = handler;
-    }
-
-    private void doExecute(Iterable<? extends TaskInfo> tasks) {
-        for (TaskInfo task : tasks) {
-            executeTask(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 {
-            task.executeWithoutThrowingTaskFailure();
-            if (task.getState().getFailure() != null) {
-                failureHandler.onTaskFailure(task);
-            } else {
-                taskInfo.executed = true;
-            }
-        } finally {
-            taskListeners.getSource().afterExecute(task, task.getState());
-        }
-    }
-
-    public boolean hasTask(Task task) {
-        assertPopulated();
-        return executionPlan.containsKey(task);
-    }
-
-    public boolean hasTask(String path) {
-        assertPopulated();
-        assert path != null && path.length() > 0;
-        for (Task task : getAllTasks()) {
-            if (task.getPath().equals(path)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public List<Task> getAllTasks() {
-        assertPopulated();
-        return new ArrayList<Task>(executionPlan.keySet());
-    }
-
-    private void assertPopulated() {
-        if (!populated) {
-            throw new IllegalStateException(
-                    "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/ExcludedTaskFilteringBuildConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
index 92ae183..fbfb721 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
@@ -26,24 +26,15 @@ 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()) {
+            TaskSelector selector = createSelector(gradle);
             final Set<Task> excludedTasks = new HashSet<Task>();
             for (String taskName : excludedTaskNames) {
-                selector.selectTasks(gradle, taskName);
-                excludedTasks.addAll(selector.getTasks());
+                excludedTasks.addAll(selector.getSelection(taskName).getTasks());
             }
             gradle.getTaskGraph().useFilter(new Spec<Task>() {
                 public boolean isSatisfiedBy(Task task) {
@@ -54,4 +45,8 @@ public class ExcludedTaskFilteringBuildConfigurationAction implements BuildConfi
 
         context.proceed();
     }
+
+    TaskSelector createSelector(GradleInternal gradle) {
+        return new TaskSelector(gradle);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/MultipleBuildFailures.java b/subprojects/core/src/main/groovy/org/gradle/execution/MultipleBuildFailures.java
new file mode 100644
index 0000000..9b0ddb5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/MultipleBuildFailures.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.execution;
+
+import org.gradle.api.internal.AbstractMultiCauseException;
+
+import java.util.List;
+
+public class MultipleBuildFailures extends AbstractMultiCauseException {
+    public MultipleBuildFailures(Iterable<? extends Throwable> causes) {
+        super("Multiple build failures", causes);
+    }
+
+    public void replaceCauses(List<? extends Throwable> causes) {
+        super.initCauses(causes);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java
index 69dac7d..f56d5f3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java
@@ -16,43 +16,22 @@
 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();
+            taskGraph.useFailureHandler(new ContinueOnFailureHandler());
         }
+
+        taskGraph.execute();
     }
 
-    private static class MultipleFailuresHandler implements TaskFailureHandler {
-        final List<Throwable> failures = new ArrayList<Throwable>();
-        
+    private static class ContinueOnFailureHandler implements TaskFailureHandler {
         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);
-            }
+            // Do nothing
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
index 3a493a4..fb501e2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
@@ -15,24 +15,15 @@
  */
 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.execution.commandline.CommandLineTaskParser;
 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
@@ -70,41 +61,7 @@ public class TaskNameResolvingBuildConfigurationAction implements BuildConfigura
     }
 
     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;
+        TaskSelector selector = new TaskSelector(gradle, taskNameResolver);
+        return new CommandLineTaskParser().parseTasks(paths, selector);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
index dc873f0..a1ade7b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
@@ -28,18 +28,18 @@ import java.util.Set;
 
 public class TaskSelector {
     private final TaskNameResolver taskNameResolver;
-    private Set<Task> tasks;
-    private String taskName;
+    private final GradleInternal gradle;
 
-    public TaskSelector() {
-        this(new TaskNameResolver());
+    public TaskSelector(GradleInternal gradle) {
+        this(gradle, new TaskNameResolver());
     }
 
-    public TaskSelector(TaskNameResolver taskNameResolver) {
+    public TaskSelector(GradleInternal gradle, TaskNameResolver taskNameResolver) {
         this.taskNameResolver = taskNameResolver;
+        this.gradle = gradle;
     }
 
-    public void selectTasks(GradleInternal gradle, String path) {
+    public TaskSelection getSelection(String path) {
         SetMultimap<String, Task> tasksByName;
         String baseName;
         String prefix;
@@ -64,9 +64,7 @@ public class TaskSelector {
         Set<Task> tasks = tasksByName.get(baseName);
         if (!tasks.isEmpty()) {
             // An exact match
-            this.tasks = tasks;
-            this.taskName = path;
-            return;
+            return new TaskSelection(path, tasks);
         }
 
         NameMatcher matcher = new NameMatcher();
@@ -74,22 +72,12 @@ public class TaskSelector {
 
         if (actualName != null) {
             // A partial match
-            this.tasks = tasksByName.get(actualName);
-            this.taskName = prefix + actualName;
-            return;
+            return new TaskSelection(prefix + actualName, tasksByName.get(actualName));
         }
 
         throw new TaskSelectionException(matcher.formatErrorMessage("task", project));
     }
 
-    public String getTaskName() {
-        return taskName;
-    }
-
-    public Set<Task> getTasks() {
-        return tasks;
-    }
-
     private static ProjectInternal findProject(ProjectInternal startFrom, String path) {
         if (path.equals(Project.PATH_SEPARATOR)) {
             return startFrom.getRootProject();
@@ -114,4 +102,22 @@ public class TaskSelector {
 
         return (ProjectInternal) current;
     }
+
+    public static class TaskSelection {
+        private String taskName;
+        private Set<Task> tasks;
+
+        public TaskSelection(String taskName, Set<Task> tasks) {
+            this.taskName = taskName;
+            this.tasks = tasks;
+        }
+
+        public String getTaskName() {
+            return taskName;
+        }
+
+        public Set<Task> getTasks() {
+            return tasks;
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
new file mode 100644
index 0000000..0876157
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.cli.ParsedCommandLineOption;
+import org.gradle.util.JavaMethod;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 9/5/12
+ */
+public class CommandLineTaskConfigurer {
+
+    public List<String> configureTasks(Collection<Task> tasks, List<String> arguments) {
+        assert !tasks.isEmpty();
+        if (!shouldConfigureWith(arguments)) {
+            return arguments;
+        }
+        return configureTasksNow(tasks, arguments);
+    }
+
+    private List<String> configureTasksNow(Collection<Task> tasks, List<String> arguments) {
+        List<String> remainingArguments = null;
+        for (Task task : tasks) {
+            Map<String, JavaMethod<Object, ?>> options = new HashMap<String, JavaMethod<Object, ?>>();
+            CommandLineParser parser = new CommandLineParser();
+            for (Class<?> type = task.getClass(); type != Object.class; type = type.getSuperclass()) {
+                for (Method method : type.getDeclaredMethods()) {
+                    CommandLineOption commandLineOption = method.getAnnotation(CommandLineOption.class);
+                    if (commandLineOption != null) {
+                        String optionName = commandLineOption.options()[0];
+                        org.gradle.cli.CommandLineOption option = parser.option(optionName);
+                        option.hasDescription(commandLineOption.description());
+                        if (method.getParameterTypes().length > 0 && !hasSingleBooleanParameter(method)) {
+                            option.hasArgument();
+                        }
+                        options.put(optionName, JavaMethod.create(Object.class, Object.class, method));
+                    }
+                }
+            }
+
+            ParsedCommandLine parsed = null;
+            try {
+                parsed = parser.parse(arguments);
+            } catch (CommandLineArgumentException e) {
+                //we expect that all options must be applicable for each task
+                throw new GradleException("Problem configuring task " + task.getPath() + " from command line. " + e.getMessage(), e);
+            }
+            for (Map.Entry<String, JavaMethod<Object, ?>> entry : options.entrySet()) {
+                if (parsed.hasOption(entry.getKey())) {
+                    ParsedCommandLineOption o = parsed.option(entry.getKey());
+                    if (o.hasValue()) {
+                        entry.getValue().invoke(task, o.getValue());
+                    } else {
+                        entry.getValue().invoke(task, true);
+                    }
+                }
+            }
+            //since
+            assert remainingArguments == null || remainingArguments.equals(parsed.getExtraArguments())
+                : "we expect all options to be consumed by each task so remainingArguments should be the same for each task";
+            remainingArguments = parsed.getExtraArguments();
+        }
+        return remainingArguments;
+    }
+
+    private boolean hasSingleBooleanParameter(Method method) {
+        if (method.getParameterTypes().length != 1) {
+            return false;
+        }
+        Class<?> type = method.getParameterTypes()[0];
+        return type == Boolean.class || type == Boolean.TYPE;
+    }
+
+    private boolean shouldConfigureWith(List<String> arguments) {
+        for (String a : arguments) {
+            if (a.startsWith("--")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.java
new file mode 100644
index 0000000..1597655
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/commandline/CommandLineTaskParser.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.execution.commandline;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.execution.TaskSelector;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 10/8/12
+ */
+public class CommandLineTaskParser {
+
+    CommandLineTaskConfigurer taskConfigurer =  new CommandLineTaskConfigurer();
+
+    public Multimap<String, Task> parseTasks(List<String> taskPaths, TaskSelector taskSelector) {
+        validateTaskPaths(taskPaths);
+        SetMultimap<String, Task> out = LinkedHashMultimap.create();
+        List<String> remainingPaths = new LinkedList<String>(taskPaths);
+        while (!remainingPaths.isEmpty()) {
+            String path = remainingPaths.remove(0);
+            TaskSelector.TaskSelection selection = taskSelector.getSelection(path);
+            Set<Task> tasks = selection.getTasks();
+            remainingPaths = taskConfigurer.configureTasks(tasks, remainingPaths);
+
+            out.putAll(selection.getTaskName(), tasks);
+        }
+        return out;
+    }
+
+    private void validateTaskPaths(List<String> taskPaths) {
+        List<String> invalidTasks = new LinkedList<String>();
+        for (String path : taskPaths) {
+            if (path.startsWith("-") && !path.startsWith("--")) {
+                invalidTasks.add(path);
+            }
+        }
+        if (!invalidTasks.isEmpty()) {
+            throw new GradleException("Incorrect command line arguments: " + invalidTasks + ". Task options require double dash, for example: 'gradle tasks --all'.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
new file mode 100644
index 0000000..1c02a73
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taskgraph;
+
+import org.gradle.api.CircularReferenceException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.tasks.CachingTaskDependencyResolveContext;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.execution.MultipleBuildFailures;
+import org.gradle.execution.TaskFailureHandler;
+import org.gradle.internal.UncheckedException;
+
+import java.util.*;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A reusable implementation of TaskExecutionPlan. The {@link #addToTaskGraph(java.util.Collection)} and {@link #clear()} methods are NOT threadsafe, and callers must synchronize
+ * access to these methods.
+ */
+class DefaultTaskExecutionPlan implements TaskExecutionPlan {
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final LinkedHashMap<Task, TaskInfo> executionPlan = new LinkedHashMap<Task, TaskInfo>();
+    private final List<Throwable> failures = new ArrayList<Throwable>();
+    private Spec<? super Task> filter = Specs.satisfyAll();
+
+    private TaskFailureHandler failureHandler = new RethrowingFailureHandler();
+
+    public void addToTaskGraph(Collection<? extends Task> tasks) {
+        List<Task> queue = new ArrayList<Task>(tasks);
+        Collections.sort(queue);
+
+        Set<Task> visiting = new HashSet<Task>();
+        CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
+
+        while (!queue.isEmpty()) {
+            Task task = queue.get(0);
+            if (!filter.isSatisfiedBy(task)) {
+                // Filtered - skip
+                queue.remove(0);
+                continue;
+            }
+            if (executionPlan.containsKey(task)) {
+                // Already in plan - skip
+                queue.remove(0);
+                continue;
+            }
+
+            if (visiting.add(task)) {
+                // Have not seen this task before - add its dependencies to the head of the queue and leave this
+                // task in the queue
+                Set<Task> dependsOnTasks = new TreeSet<Task>(Collections.reverseOrder());
+                dependsOnTasks.addAll(context.getDependencies(task));
+                for (Task dependsOnTask : dependsOnTasks) {
+                    if (visiting.contains(dependsOnTask)) {
+                        throw new CircularReferenceException(String.format(
+                                "Circular dependency between tasks. Cycle includes [%s, %s].", task, dependsOnTask));
+                    }
+                    queue.add(0, dependsOnTask);
+                }
+            } else {
+                // Have visited this task's dependencies - add it to the end of the plan
+                queue.remove(0);
+                visiting.remove(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));
+            }
+        }
+    }
+
+    public void clear() {
+        lock.lock();
+        try {
+            executionPlan.clear();
+            failures.clear();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public List<Task> getTasks() {
+        return new ArrayList<Task>(executionPlan.keySet());
+    }
+
+    public void useFilter(Spec<? super Task> filter) {
+        this.filter = filter;
+    }
+
+    public void useFailureHandler(TaskFailureHandler handler) {
+        this.failureHandler = handler;
+    }
+
+    public TaskInfo getTaskToExecute(Spec<TaskInfo> criteria) {
+        lock.lock();
+        try {
+
+            TaskInfo nextMatching;
+            while ((nextMatching = getNextReadyAndMatching(criteria)) != null) {
+                while (!nextMatching.allDependenciesComplete()) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                // The task state could have been modified while we waited for dependency completion. Check that it is still 'ready'.
+                if (!nextMatching.isReady()) {
+                    continue;
+                }
+
+                if (nextMatching.allDependenciesSuccessful()) {
+                    nextMatching.startExecution();
+                    return nextMatching;
+                } else {
+                    nextMatching.skipExecution();
+                    condition.signalAll();
+                }
+            }
+
+            return null;
+
+        } finally {
+            lock.unlock();
+        }
+
+    }
+
+    private TaskInfo getNextReadyAndMatching(Spec<TaskInfo> criteria) {
+        for (TaskInfo taskInfo : executionPlan.values()) {
+            if (taskInfo.isReady() && criteria.isSatisfiedBy(taskInfo)) {
+                return taskInfo;
+            }
+        }
+        return null;
+    }
+
+    public void taskComplete(TaskInfo taskInfo) {
+        lock.lock();
+        try {
+            if (taskInfo.isFailed()) {
+                handleFailure(taskInfo);
+            }
+
+            taskInfo.finishExecution();
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void handleFailure(TaskInfo taskInfo) {
+        Throwable executionFailure = taskInfo.getExecutionFailure();
+        if (executionFailure != null) {
+            // Always abort execution for an execution failure (as opposed to a task failure)
+            abortExecution();
+            this.failures.add(executionFailure);
+            return;
+        }
+
+        // Task failure
+        try {
+            failureHandler.onTaskFailure(taskInfo.getTask());
+            this.failures.add(taskInfo.getTaskFailure());
+        } catch (Exception e) {
+            // If the failure handler rethrows exception, then execution of other tasks is aborted. (--continue will collect failures)
+            abortExecution();
+            this.failures.add(e);
+        }
+    }
+
+    private void abortExecution() {
+        // Allow currently executing tasks to complete, but skip everything else.
+        for (TaskInfo taskInfo : executionPlan.values()) {
+            if (taskInfo.isReady()) {
+                taskInfo.skipExecution();
+            }
+        }
+    }
+
+    public void awaitCompletion() {
+        lock.lock();
+        try {
+            while (!allTasksComplete()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            rethrowFailures();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void rethrowFailures() {
+        if (failures.isEmpty()) {
+            return;
+        }
+
+        if (failures.size() > 1) {
+            throw new MultipleBuildFailures(failures);
+        }
+
+        throw UncheckedException.throwAsUncheckedException(failures.get(0));
+    }
+
+    private boolean allTasksComplete() {
+        for (TaskInfo taskInfo : executionPlan.values()) {
+            if (!taskInfo.isComplete()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static class RethrowingFailureHandler implements TaskFailureHandler {
+        public void onTaskFailure(Task task) {
+            task.getState().rethrowFailure();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
new file mode 100644
index 0000000..92d6650
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuter.java
@@ -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.execution.taskgraph;
+
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.specs.Spec;
+import org.gradle.execution.TaskFailureHandler;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
+    private static Logger logger = LoggerFactory.getLogger(DefaultTaskGraphExecuter.class);
+
+    private final TaskPlanExecutor taskPlanExecutor;
+    private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
+    private final ListenerBroadcast<TaskExecutionListener> taskListeners;
+    private final DefaultTaskExecutionPlan taskExecutionPlan = new DefaultTaskExecutionPlan();
+    private boolean populated;
+
+    public DefaultTaskGraphExecuter(ListenerManager listenerManager, TaskPlanExecutor taskPlanExecutor) {
+        this.taskPlanExecutor = taskPlanExecutor;
+        graphListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionGraphListener.class);
+        taskListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionListener.class);
+    }
+
+    public void useFilter(Spec<? super Task> filter) {
+        taskExecutionPlan.useFilter(filter);
+    }
+
+    public void useFailureHandler(TaskFailureHandler handler) {
+        taskExecutionPlan.useFailureHandler(handler);
+    }
+
+    public void addTasks(Iterable<? extends Task> tasks) {
+        assert tasks != null;
+
+        Clock clock = new Clock();
+
+        Set<Task> taskSet = new LinkedHashSet<Task>();
+        for (Task task : tasks) {
+            taskSet.add(task);
+        }
+        taskExecutionPlan.addToTaskGraph(taskSet);
+        populated = true;
+
+        logger.debug("Timing: Creating the DAG took " + clock.getTime());
+    }
+
+    public void execute() {
+        assertPopulated();
+        Clock clock = new Clock();
+
+        graphListeners.getSource().graphPopulated(this);
+        try {
+            taskPlanExecutor.process(taskExecutionPlan, taskListeners.getSource());
+            logger.debug("Timing: Executing the DAG took " + clock.getTime());
+        } finally {
+            taskExecutionPlan.clear();
+        }
+    }
+
+    public void addTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
+        graphListeners.add(listener);
+    }
+
+    public void removeTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
+        graphListeners.remove(listener);
+    }
+
+    public void whenReady(final Closure closure) {
+        graphListeners.add("graphPopulated", closure);
+    }
+
+    public void addTaskExecutionListener(TaskExecutionListener listener) {
+        taskListeners.add(listener);
+    }
+
+    public void removeTaskExecutionListener(TaskExecutionListener listener) {
+        taskListeners.remove(listener);
+    }
+
+    public void beforeTask(final Closure closure) {
+        taskListeners.add("beforeExecute", closure);
+    }
+
+    public void afterTask(final Closure closure) {
+        taskListeners.add("afterExecute", closure);
+    }
+
+    public boolean hasTask(Task task) {
+        assertPopulated();
+        return taskExecutionPlan.getTasks().contains(task);
+    }
+
+    public boolean hasTask(String path) {
+        assertPopulated();
+        assert path != null && path.length() > 0;
+        for (Task task : taskExecutionPlan.getTasks()) {
+            if (task.getPath().equals(path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public List<Task> getAllTasks() {
+        assertPopulated();
+        return taskExecutionPlan.getTasks();
+    }
+
+    private void assertPopulated() {
+        if (!populated) {
+            throw new IllegalStateException(
+                    "Task information is not available, as this task execution graph has not been populated.");
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutor.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutor.java
new file mode 100644
index 0000000..b353a6d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskPlanExecutor.java
@@ -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.execution.taskgraph;
+
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+class DefaultTaskPlanExecutor implements TaskPlanExecutor {
+
+    public void process(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+        Spec<TaskInfo> anyTask = Specs.satisfyAll();
+        TaskInfo taskInfo = taskExecutionPlan.getTaskToExecute(anyTask);
+        while (taskInfo != null) {
+            processTask(taskInfo, taskExecutionPlan, taskListener);
+            taskInfo = taskExecutionPlan.getTaskToExecute(anyTask);
+        }
+        taskExecutionPlan.awaitCompletion();
+    }
+
+    protected void processTask(TaskInfo taskInfo, TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+        try {
+            executeTask(taskInfo, taskListener);
+        } catch (Throwable e) {
+            taskInfo.setExecutionFailure(e);
+        } finally {
+            taskExecutionPlan.taskComplete(taskInfo);
+        }
+    }
+
+    // TODO:PARALLEL It would be good to move this logic into a TaskExecuter wrapper, but we'd need a way to give it a TaskExecutionListener that
+    // is wired to the various add/remove listener methods on TaskExecutionGraph
+    private void executeTask(TaskInfo taskInfo, TaskExecutionListener taskListener) {
+        TaskInternal task = taskInfo.getTask();
+        taskListener.beforeExecute(task);
+        try {
+            task.executeWithoutThrowingTaskFailure();
+        } finally {
+            taskListener.afterExecute(task, task.getState());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ExecutionOptions.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ExecutionOptions.java
new file mode 100644
index 0000000..caa6512
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ExecutionOptions.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.execution.taskgraph;
+
+class ExecutionOptions {
+    private final int parallelExecutors;
+
+    public ExecutionOptions(int parallelExecutors) {
+        this.parallelExecutors = parallelExecutors;
+    }
+
+    public boolean executeProjectsInParallel() {
+        return parallelExecutors != 0;
+    }
+
+    public int numberOfParallelThreads() {
+        if (parallelExecutors == -1) {
+            return Runtime.getRuntime().availableProcessors();
+        }
+        return parallelExecutors;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ParallelTaskPlanExecutor.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ParallelTaskPlanExecutor.java
new file mode 100644
index 0000000..419c848
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/ParallelTaskPlanExecutor.java
@@ -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.execution.taskgraph;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
+import org.gradle.api.specs.Spec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ParallelTaskPlanExecutor.class);
+
+    private final List<Thread> executorThreads = new ArrayList<Thread>();
+    private final TaskArtifactStateCacheAccess stateCacheAccess;
+    private final int executorCount;
+
+    public ParallelTaskPlanExecutor(TaskArtifactStateCacheAccess cacheAccess, int numberOfParallelExecutors) {
+        if (numberOfParallelExecutors < 1) {
+            throw new IllegalArgumentException("Not a valid number of parallel executors: " + numberOfParallelExecutors);
+        }
+
+        LOGGER.info("Using {} parallel executor threads", numberOfParallelExecutors);
+
+        this.stateCacheAccess = cacheAccess;
+        this.executorCount = numberOfParallelExecutors;
+    }
+
+    public void process(final TaskExecutionPlan taskExecutionPlan, final TaskExecutionListener taskListener) {
+        stateCacheAccess.longRunningOperation("Executing all tasks", new Runnable() {
+            public void run() {
+                doProcess(taskExecutionPlan, taskListener);
+                // TODO This needs to wait until all tasks have been executed, not just started....
+                taskExecutionPlan.awaitCompletion();
+            }
+        });
+    }
+
+    private void doProcess(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+        List<Project> projects = getAllProjects(taskExecutionPlan);
+        int numExecutors = Math.min(executorCount, projects.size());
+
+        for (int i = 0; i < numExecutors; i++) {
+            TaskExecutorWorker worker = new TaskExecutorWorker(taskExecutionPlan, taskListener);
+
+            for (int j = i; j < projects.size(); j += numExecutors) {
+                worker.addProject(projects.get(j));
+            }
+
+            executorThreads.add(new Thread(worker));
+        }
+
+        for (Thread executorThread : executorThreads) {
+            // TODO A bunch more stuff to contextualise the thread
+            executorThread.start();
+        }
+    }
+
+    private List<Project> getAllProjects(TaskExecutionPlan taskExecutionPlan) {
+        final Set<Project> uniqueProjects = new LinkedHashSet<Project>();
+        for (Task task : taskExecutionPlan.getTasks()) {
+            uniqueProjects.add(task.getProject());
+        }
+        return new ArrayList<Project>(uniqueProjects);
+    }
+
+    private class TaskExecutorWorker implements Runnable {
+        private final TaskExecutionPlan taskExecutionPlan;
+        private final TaskExecutionListener taskListener;
+
+        private final List<Project> projects = new ArrayList<Project>();
+
+        private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
+            this.taskExecutionPlan = taskExecutionPlan;
+            this.taskListener = taskListener;
+        }
+
+        public void run() {
+            TaskInfo taskInfo;
+            while ((taskInfo = taskExecutionPlan.getTaskToExecute(getTaskSpec())) != null) {
+                executeTaskWithCacheLock(taskInfo);
+            }
+
+            LOGGER.info(Thread.currentThread() + " stopping");
+        }
+
+        private void executeTaskWithCacheLock(final TaskInfo taskInfo) {
+            final String taskPath = taskInfo.getTask().getPath();
+            LOGGER.info(taskPath + " (" + Thread.currentThread() + " - start");
+            stateCacheAccess.useCache("Executing " + taskPath, new Runnable() {
+                public void run() {
+                    processTask(taskInfo, taskExecutionPlan, taskListener);
+                }
+            });
+            LOGGER.info(taskPath + " (" + Thread.currentThread() + ") - complete");
+        }
+
+        public void addProject(Project project) {
+            projects.add(project);
+        }
+
+        private Spec<TaskInfo> getTaskSpec() {
+            return new Spec<TaskInfo>() {
+                public boolean isSatisfiedBy(TaskInfo element) {
+                    return projects.contains(element.getTask().getProject());
+                }
+            };
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
new file mode 100644
index 0000000..3119e02
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.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.execution.taskgraph;
+
+import org.gradle.api.Task;
+import org.gradle.api.specs.Spec;
+
+import java.util.List;
+
+/**
+ * Represents a graph of dependent tasks, returned in execution order.
+ */
+public interface TaskExecutionPlan {
+    /**
+     * Provides a ready-to-execute task that matches the specified criteria. A task is ready-to-execute if all of it's dependencies have been completed successfully.
+     * If the next matching task is not ready-to-execute, this method will block until it is ready.
+     * If no tasks remain that match the criteria, null will be returned.
+     * @param criteria Only tasks matching this Spec will be returned.
+     * @return The next matching task, or null if no matching tasks remain.
+     */
+    TaskInfo getTaskToExecute(Spec<TaskInfo> criteria);
+
+    /**
+     * Signals to the plan that execution of this task has completed. Execution is complete if the task succeeds, fails, or an exception is thrown during execution.
+     * @param task the completed task.
+     */
+    void taskComplete(TaskInfo task);
+
+    /**
+     * Blocks until all tasks in the plan have been processed. This method will only return when every task in the plan has either completed, failed or been skipped.
+     */
+    void awaitCompletion();
+
+    /**
+     * @return The list of all available tasks. This includes tasks that have not yet been executed, as well as tasks that have been processed.
+     */
+    List<Task> getTasks();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskInfo.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskInfo.java
new file mode 100644
index 0000000..ea7b9fb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskInfo.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taskgraph;
+
+import org.gradle.api.internal.TaskInternal;
+
+import java.util.Set;
+
+class TaskInfo {
+
+    private enum TaskExecutionState {
+        READY, EXECUTING, EXECUTED, SKIPPED
+    }
+
+    private final TaskInternal task;
+    private final Set<TaskInfo> dependencies;
+    private TaskExecutionState state;
+    private Throwable executionFailure;
+
+    public TaskInfo(TaskInternal task, Set<TaskInfo> dependencies) {
+        this.task = task;
+        this.dependencies = dependencies;
+        this.state = TaskExecutionState.READY;
+    }
+
+    public TaskInternal getTask() {
+        return task;
+    }
+
+    public Set<TaskInfo> getDependencies() {
+        return dependencies;
+    }
+
+    public boolean isReady() {
+        return state == TaskExecutionState.READY;
+    }
+
+    public boolean isComplete() {
+        return state == TaskExecutionState.EXECUTED || state == TaskExecutionState.SKIPPED;
+    }
+
+    public boolean isSuccessful() {
+        return state == TaskExecutionState.EXECUTED && !isFailed();
+    }
+
+    public boolean isFailed() {
+        return getTaskFailure() != null || getExecutionFailure() != null;
+    }
+
+    public void startExecution() {
+        assert state == TaskExecutionState.READY;
+        state = TaskExecutionState.EXECUTING;
+    }
+
+    public void finishExecution() {
+        assert state == TaskExecutionState.EXECUTING;
+        state = TaskExecutionState.EXECUTED;
+    }
+
+    public void skipExecution() {
+        assert state == TaskExecutionState.READY;
+        state = TaskExecutionState.SKIPPED;
+    }
+
+    public void setExecutionFailure(Throwable failure) {
+        assert state == TaskExecutionState.EXECUTING;
+        this.executionFailure = failure;
+    }
+
+    public Throwable getExecutionFailure() {
+        return this.executionFailure;
+    }
+
+    public Throwable getTaskFailure() {
+        return this.getTask().getState().getFailure();
+    }
+
+    public boolean allDependenciesComplete() {
+        for (TaskInfo dependency : getDependencies()) {
+            if (!dependency.isComplete()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean allDependenciesSuccessful() {
+        for (TaskInfo dependency : getDependencies()) {
+            if (!dependency.isSuccessful()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.java
new file mode 100644
index 0000000..12a0e1e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutor.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.execution.taskgraph;
+
+import org.gradle.api.execution.TaskExecutionListener;
+
+public interface TaskPlanExecutor {
+    void process(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
new file mode 100644
index 0000000..201350b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.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.execution.taskgraph;
+
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
+import org.gradle.internal.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TaskPlanExecutorFactory implements Factory<TaskPlanExecutor> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(TaskPlanExecutorFactory.class);
+
+    private final TaskArtifactStateCacheAccess taskArtifactStateCacheAccess;
+    private final int parallelThreads;
+    private final DocumentationRegistry documentationRegistry;
+
+    public TaskPlanExecutorFactory(TaskArtifactStateCacheAccess taskArtifactStateCacheAccess, int parallelThreads, DocumentationRegistry documentationRegistry) {
+        this.taskArtifactStateCacheAccess = taskArtifactStateCacheAccess;
+        this.parallelThreads = parallelThreads;
+        this.documentationRegistry = documentationRegistry;
+    }
+
+    public TaskPlanExecutor create() {
+        ExecutionOptions options = new ExecutionOptions(parallelThreads);
+        if (options.executeProjectsInParallel()) {
+            String parallelWarningMessage = String.format(
+                    "Parallel project execution is an \"incubating\" feature (%s). Many builds will not run correctly with this option.",
+                    documentationRegistry.getFeatureLifecycle()
+            );
+            LOGGER.warn(parallelWarningMessage);
+            return new ParallelTaskPlanExecutor(taskArtifactStateCacheAccess, options.numberOfParallelThreads());
+        }
+        return new DefaultTaskPlanExecutor();
+
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java
index 7da03d1..f911362 100755
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java
@@ -128,7 +128,7 @@ public abstract class DefaultScript extends BasicScript {
     }
 
     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");
+        DeprecationLogger.nagUserOfDeprecated("fileTree(Closure)", "Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
         //noinspection deprecation
         return fileOperations.fileTree(closure);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java
index 1a3148b..eade02d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java
@@ -23,9 +23,12 @@ import org.gradle.util.DeprecationLogger;
  */
 public class StatementLabelsDeprecationLogger {
     public static void log(String label, String sample) {
-        DeprecationLogger.nagUserWith(String.format("Usage of statement labels in build scripts has been deprecated "
-                + "and will no longer be supported in the next version of Gradle. In case you tried to configure a property "
-                + "named '%s', replace ':' with '=' or ' '. Otherwise it will not have the desired effect. \n\n%s",
-                label, sample));
+        DeprecationLogger.nagUserOfDeprecated(
+                "Usage of statement labels in build scripts",
+                String.format(
+                        "In case you tried to configure a property named '%s', replace ':' with '=' or ' '. Otherwise it will not have the desired effect. \n\n%s",
+                        label, sample
+                )
+        );
     }
 }
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 4948886..1aa27da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
@@ -19,8 +19,6 @@ 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.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;
@@ -28,7 +26,6 @@ import org.gradle.groovy.scripts.ScriptSource;
 
 import java.io.File;
 import java.net.URLClassLoader;
-import java.util.Map;
 
 /**
  * @author Hans Dockter
@@ -46,8 +43,6 @@ public class BaseSettings implements SettingsInternal {
 
     private DefaultProjectDescriptor rootProjectDescriptor;
 
-    private ExtensibleDynamicObject dynamicObject;
-
     private GradleInternal gradle;
     private IProjectDescriptorRegistry projectDescriptorRegistry;
 
@@ -65,7 +60,6 @@ public class BaseSettings implements SettingsInternal {
         this.startParameter = startParameter;
         this.classloader = classloader;
         rootProjectDescriptor = createProjectDescriptor(null, settingsDir.getName(), settingsDir);
-        dynamicObject = new ExtensibleDynamicObject(this);
     }
 
     @Override
@@ -188,14 +182,6 @@ public class BaseSettings implements SettingsInternal {
         this.projectDescriptorRegistry = projectDescriptorRegistry;
     }
 
-    public void addDynamicProperties(Map<String, ?> properties) {
-        dynamicObject.addProperties(properties);
-    }
-
-    protected DynamicObject getDynamicObject() {
-        return dynamicObject;
-    }
-
     public IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry() {
         return projectDescriptorRegistry;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
index 7bac31a..eac333d 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
@@ -60,6 +60,7 @@ public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
         rootClassLoader.allowPackage("org.slf4j");
         rootClassLoader.allowPackage("org.apache.commons.logging");
         rootClassLoader.allowPackage("org.apache.log4j");
+        rootClassLoader.allowPackage("javax.inject");
     }
 
     public ClassLoader getRootClassLoader() {
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 5bda2c0..c717720 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -53,6 +53,9 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
     private static final String PROJECT_CACHE_DIR = "project-cache-dir";
     private static final String RECOMPILE_SCRIPTS = "recompile-scripts";
 
+    private static final String PARALLEL = "parallel";
+    private static final String PARALLEL_THREADS = "parallel-threads";
+
     private final CommandLineConverter<LoggingConfiguration> loggingConfigurationCommandLineConverter = new LoggingCommandLineConverter();
     private final SystemPropertiesCommandLineConverter systemPropertiesCommandLineConverter = new SystemPropertiesCommandLineConverter();
     private final ProjectPropertiesCommandLineConverter projectPropertiesCommandLineConverter = new ProjectPropertiesCommandLineConverter();
@@ -78,10 +81,12 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         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(CONTINUE).hasDescription("Continues task execution after a task failure.");
         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.");
+        parser.option(PARALLEL).hasDescription("Build projects in parallel. Gradle will attempt to determine the optimal number of executor threads to use.").incubating();
+        parser.option(PARALLEL_THREADS).hasArgument().hasDescription("Build projects in parallel, using the specified number of executor threads.").incubating();
     }
 
     @Override
@@ -180,6 +185,19 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
             startParameter.setRefreshDependencies(true);
         }
 
+        if (options.hasOption(PARALLEL)) {
+            startParameter.setParallelThreadCount(-1);
+        }
+
+        if (options.hasOption(PARALLEL_THREADS)) {
+            try {
+                int parallelThreads = Integer.parseInt(options.option(PARALLEL_THREADS).getValue());
+                startParameter.setParallelThreadCount(parallelThreads);
+            } catch (NumberFormatException e) {
+                throw new CommandLineArgumentException(String.format("Not a numeric argument for %s", PARALLEL_THREADS));
+            }
+        }
+
         return startParameter;
     }
 
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 5562818..ac459fe 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -41,6 +41,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
     private final InitScriptHandler initScriptHandler;
     private final LoggingManagerInternal loggingManager;
     private final ModelConfigurationListener modelConfigurationListener;
+    private final TasksCompletionListener tasksCompletionListener;
     private final BuildExecuter buildExecuter;
 
     /**
@@ -50,7 +51,8 @@ public class DefaultGradleLauncher extends GradleLauncher {
     public DefaultGradleLauncher(GradleInternal gradle, InitScriptHandler initScriptHandler, SettingsHandler settingsHandler,
                                  BuildLoader buildLoader, BuildConfigurer buildConfigurer, BuildListener buildListener,
                                  ExceptionAnalyser exceptionAnalyser, LoggingManagerInternal loggingManager,
-                                 ModelConfigurationListener modelConfigurationListener, BuildExecuter buildExecuter) {
+                                 ModelConfigurationListener modelConfigurationListener, TasksCompletionListener tasksCompletionListener,
+                                 BuildExecuter buildExecuter) {
         this.gradle = gradle;
         this.initScriptHandler = initScriptHandler;
         this.settingsHandler = settingsHandler;
@@ -60,6 +62,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
         this.buildListener = buildListener;
         this.loggingManager = loggingManager;
         this.modelConfigurationListener = modelConfigurationListener;
+        this.tasksCompletionListener = tasksCompletionListener;
         this.buildExecuter = buildExecuter;
     }
 
@@ -153,6 +156,7 @@ public class DefaultGradleLauncher extends GradleLauncher {
 
         // Execute build
         buildExecuter.execute();
+        tasksCompletionListener.onTasksFinished(gradle);
 
         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 8c27b1d..6116dc3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -106,7 +106,6 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
         LoggingManagerInternal loggingManager = serviceRegistry.newInstance(LoggingManagerInternal.class);
         loggingManager.setLevel(startParameter.getLogLevel());
-        loggingManager.colorStdOutAndStdErr(startParameter.isColorOutput());
 
         //this hooks up the ListenerManager and LoggingConfigurer so you can call Gradle.addListener() with a StandardOutputListener.
         loggingManager.addStandardOutputListener(listenerManager.getBroadcaster(StandardOutputListener.class));
@@ -142,6 +141,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
                 serviceRegistry.get(ExceptionAnalyser.class),
                 loggingManager,
                 listenerManager.getBroadcaster(ModelConfigurationListener.class),
+                listenerManager.getBroadcaster(TasksCompletionListener.class),
                 gradle.getServices().get(BuildExecuter.class));
     }
 
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 206e40b..b5aedfe 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
@@ -33,11 +33,4 @@ public class DefaultSettings extends BaseSettings {
       super(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter)
     }
 
-    def propertyMissing(String property) {
-        return dynamicObject.getProperty(property)
-    }
-
-    void setProperty(String name, value) {
-        dynamicObject.setProperty(name, value)
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
index 5f5256b..2c6cbf7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
@@ -20,23 +20,37 @@ import org.gradle.api.artifacts.ResolvableDependencies;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 
+// TODO:DAZ Think about a better way to do thread-safety here, maybe
 public class DependencyResolutionLogger implements DependencyResolutionListener {
     private final ProgressLoggerFactory loggerFactory;
-    private ProgressLogger logger;
+    private final ThreadLocal<ProgressLogger> progressLogger = new ThreadLocal<ProgressLogger>();
 
     public DependencyResolutionLogger(ProgressLoggerFactory loggerFactory) {
         this.loggerFactory = loggerFactory;
     }
 
     public void beforeResolve(ResolvableDependencies dependencies) {
-        logger = loggerFactory.newOperation(DependencyResolutionLogger.class);
+        checkLogger(false);
+        ProgressLogger logger = loggerFactory.newOperation(DependencyResolutionLogger.class);
         logger.setDescription(String.format("Resolve %s", dependencies));
         logger.setShortDescription(String.format("Resolving %s", dependencies));
         logger.started();
+        progressLogger.set(logger);
     }
 
     public void afterResolve(ResolvableDependencies dependencies) {
-        logger.completed();
-        logger = null;
+        checkLogger(true);
+        progressLogger.get().completed();
+        progressLogger.remove();
+    }
+
+    private void checkLogger(boolean shouldExist) {
+        ProgressLogger logger = progressLogger.get();
+        if (shouldExist && logger == null) {
+            throw new IllegalStateException("Logging operation not started");
+        }
+        if (!shouldExist && logger != null) {
+            throw new IllegalStateException("Logging operation already in progress");
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java
index 52362c6..4594770 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java
@@ -19,7 +19,7 @@ import org.gradle.api.internal.GradleInternal;
 
 public interface ModelConfigurationListener {
     /**
-     * Invoked when the model has been configured. This listener should not do any further configuration.
+     * Invoked when the model has been configured successfully. This listener should not do any further configuration.
      */
     void onConfigure(GradleInternal model);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/MultipleBuildFailuresExceptionAnalyser.java b/subprojects/core/src/main/groovy/org/gradle/initialization/MultipleBuildFailuresExceptionAnalyser.java
new file mode 100644
index 0000000..67f4d84
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/MultipleBuildFailuresExceptionAnalyser.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.initialization;
+
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.execution.MultipleBuildFailures;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An exception analyser that deals specifically with MultipleBuildFailures and transforms each component failure.
+ */
+public class MultipleBuildFailuresExceptionAnalyser implements ExceptionAnalyser {
+    private final ExceptionAnalyser delegate;
+
+    public MultipleBuildFailuresExceptionAnalyser(ExceptionAnalyser delegate) {
+        this.delegate = delegate;
+    }
+
+    public Throwable transform(Throwable exception) {
+        // TODO:PARALLEL Make MultipleBuildFailures a generic concept (annotation? marker interface?)
+        if (exception instanceof MultipleBuildFailures) {
+            MultipleBuildFailures multipleBuildFailures = (MultipleBuildFailures) exception;
+            List<Throwable> transformedCauses = new ArrayList<Throwable>(multipleBuildFailures.getCauses().size());
+            for (Throwable cause : multipleBuildFailures.getCauses()) {
+                transformedCauses.add(transform(cause));
+            }
+            multipleBuildFailures.replaceCauses(transformedCauses);
+            return exception;
+        }
+
+        return delegate.transform(exception);
+    }
+}
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 9bd2796..ca633e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
@@ -17,9 +17,9 @@
 package org.gradle.initialization;
 
 import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.*;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.io.File;
 import java.net.URLClassLoader;
@@ -29,18 +29,24 @@ import java.util.Map;
  * @author Hans Dockter
  */
 public class SettingsFactory {
-    private IProjectDescriptorRegistry projectDescriptorRegistry;
+    private final IProjectDescriptorRegistry projectDescriptorRegistry;
+    private final Instantiator instantiator;
 
-    public SettingsFactory(IProjectDescriptorRegistry projectDescriptorRegistry) {
+    public SettingsFactory(IProjectDescriptorRegistry projectDescriptorRegistry, Instantiator instantiator) {
         this.projectDescriptorRegistry = projectDescriptorRegistry;
+        this.instantiator = instantiator;
     }
 
     public SettingsInternal createSettings(GradleInternal gradle, File settingsDir, ScriptSource settingsScript,
                                            Map<String, String> gradleProperties, StartParameter startParameter,
                                            URLClassLoader classloader) {
-        DefaultSettings settings = new DefaultSettings(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter);
 
-        settings.addDynamicProperties(gradleProperties);
+        DefaultSettings settings = instantiator.newInstance(DefaultSettings.class,
+                gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter
+        );
+
+        DynamicObject dynamicObject = ((DynamicObjectAware) settings).getAsDynamicObject();
+        ((ExtensibleDynamicObject) dynamicObject).addProperties(gradleProperties);
         return settings;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/TasksCompletionListener.java b/subprojects/core/src/main/groovy/org/gradle/initialization/TasksCompletionListener.java
new file mode 100644
index 0000000..cc0fdd1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/TasksCompletionListener.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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+
+public interface TasksCompletionListener {
+    /**
+     * Notified when all task execution has completed successfully.
+     */
+    void onTasksFinished(GradleInternal gradle);
+}
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 92f8ad3..9d4248a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
@@ -16,55 +16,87 @@
 
 package org.gradle.logging;
 
-import org.gradle.StartParameter;
+import org.gradle.api.internal.Actions;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.internal.Factory;
 import org.gradle.internal.TimeProvider;
 import org.gradle.internal.TrueTimeProvider;
-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.logback.LogbackLoggingConfigurer;
 
 /**
- * A {@link org.gradle.internal.service.ServiceRegistry} implementation which provides the logging services.
+ * A {@link org.gradle.internal.service.ServiceRegistry} implementation that provides the logging services. To use this:
+ *
+ * <ol>
+ *     <li>Create an instance using one of the static factory methods below.</li>
+ *     <li>Create an instance of {@link LoggingManagerInternal}.</li>
+ *     <li>Configure the logging manager as appropriate.</li>
+ *     <li>Start the logging manager using {@link org.gradle.logging.LoggingManagerInternal#start()}.</li>
+ *     <li>When finished, stop the logging manager using {@link LoggingManagerInternal#stop()}.</li>
+ * </ol>
  */
-public class LoggingServiceRegistry extends DefaultServiceRegistry {
+public abstract class LoggingServiceRegistry extends DefaultServiceRegistry {
     private TextStreamOutputEventListener stdoutListener;
-    private final boolean detectConsole;
-    private final boolean isEmbedded;
-
-    LoggingServiceRegistry() {
-        this(true, false);
-    }
-
-    LoggingServiceRegistry(boolean detectConsole, boolean isEmbedded) {
-        this.detectConsole = detectConsole;
-        this.isEmbedded = isEmbedded;
-        stdoutListener = new TextStreamOutputEventListener(get(OutputEventListener.class));
-    }
 
     /**
-     * Creates a set of logging services which are suitable to use in a command-line process.
+     * Creates a set of logging services which are suitable to use in a command-line process. In particular:
+     *
+     * <ul>
+     *     <li>Replaces System.out and System.err with implementations that route output through the logging system.</li>
+     *     <li>Configures slf4j, logback, log4j and java util logging to route log messages through the logging system.</li>
+     *     <li>Routes logging output to the original System.out and System.err.</li>
+     * </ul>
+     *
+     * <p>Does nothing until started.</p>
+     *
+     * <p>Allows dynamic and colored output to be written to the console. Use {@link LoggingManagerInternal#attachConsole(boolean)} to enable this.</p>
      */
     public static LoggingServiceRegistry newCommandLineProcessLogging() {
-        return new LoggingServiceRegistry(true, false);
+        return new CommandLineLogging();
     }
 
     /**
-     * Creates a set of logging services which are suitable to use in a child process. Does not attempt to use any terminal trickery.
+     * Creates a set of logging services which are suitable to use globally in a process. In particular:
+     *
+     * <ul>
+     *     <li>Replaces System.out and System.err with implementations that route output through the logging system.</li>
+     *     <li>Configures slf4j, logback, log4j and java util logging to route log messages through the logging system.</li>
+     *     <li>Routes logging output to the original System.out and System.err.</li>
+     * </ul>
+     *
+     * <p>Does nothing until started.</p>
      */
-    public static LoggingServiceRegistry newChildProcessLogging() {
-        return new LoggingServiceRegistry(false, false);
+    public static LoggingServiceRegistry newProcessLogging() {
+        return new ChildProcessLogging();
     }
 
     /**
-     * Creates a set of logging services which are suitable to use embedded in another application. Does not attempt to use any terminal trickery.
+     * Creates a set of logging services which are suitable to use embedded in another application. In particular:
+     *
+     * <ul>
+     *     <li>Routes logging output to System.out and System.err.</li>
+     *     <li>Configures slf4j and logback.</li>
+     * </ul>
+     *
+     * <p>Does not:</p>
+     *
+     * <ul>
+     *     <li>Replace System.out and System.err to capture output written to these destinations.</li>
+     *     <li>Configure log4j or java util logging.</li>
+     * </ul>
+     *
+     * <p>Does nothing until started.</p>
      */
     public static LoggingServiceRegistry newEmbeddableLogging() {
-        return new LoggingServiceRegistry(false, true);
+        return new EmbeddedLogging();
+    }
+
+    /**
+     * Creates a set of logging services to set up a new logging scope. Does not configure any static state.
+     */
+    public LoggingServiceRegistry newLogging() {
+        return new NestedLogging();
     }
 
     protected CommandLineConverter<LoggingConfiguration> createCommandLineConverter() {
@@ -75,59 +107,74 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
         return new TrueTimeProvider();
     }
 
-    protected StdOutLoggingSystem createStdOutLoggingSystem() {
-        if (isEmbedded) {
-            return new NoOpLoggingSystem();
-        }
-        return new DefaultStdOutLoggingSystem(stdoutListener, get(TimeProvider.class));
-    }
-
     protected StyledTextOutputFactory createStyledTextOutputFactory() {
-        return new DefaultStyledTextOutputFactory(stdoutListener, get(TimeProvider.class));
+        return new DefaultStyledTextOutputFactory(getStdoutListener(), get(TimeProvider.class));
     }
 
-    protected StdErrLoggingSystem createStdErrLoggingSystem() {
-        if (isEmbedded) {
-            return new NoOpLoggingSystem();
+    protected TextStreamOutputEventListener getStdoutListener() {
+        if (stdoutListener == null) {
+            stdoutListener = new TextStreamOutputEventListener(get(OutputEventListener.class));
         }
-        TextStreamOutputEventListener listener = new TextStreamOutputEventListener(get(OutputEventListener.class));
-        return new DefaultStdErrLoggingSystem(listener, get(TimeProvider.class));
+        return stdoutListener;
     }
 
     protected ProgressLoggerFactory createProgressLoggerFactory() {
         return new DefaultProgressLoggerFactory(new ProgressLoggingBridge(get(OutputEventListener.class)), get(TimeProvider.class));
     }
 
-    protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
-        OutputEventRenderer renderer = get(OutputEventRenderer.class);
-        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 LogbackLoggingConfigurer(renderer));
-            compositeConfigurer.add(new JavaUtilLoggingConfigurer());
-            return new DefaultLoggingManagerFactory(compositeConfigurer, renderer, getStdOutLoggingSystem(), getStdErrLoggingSystem());
-        } else {
-            return new EmbeddedLoggingManagerFactory(renderer);
+    protected abstract Factory<LoggingManagerInternal> createLoggingManagerFactory();
+
+    protected OutputEventRenderer createOutputEventRenderer() {
+        OutputEventRenderer renderer = new OutputEventRenderer(Actions.doNothing());
+        renderer.addStandardOutputAndError();
+        return renderer;
+    }
+
+    private static class ChildProcessLogging extends LoggingServiceRegistry {
+        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
+            OutputEventRenderer renderer = get(OutputEventRenderer.class);
+            // Configure logback and java util logging, and capture stdout and stderr
+            LoggingSystem stdout = new DefaultStdOutLoggingSystem(getStdoutListener(), get(TimeProvider.class));
+            LoggingSystem stderr = new DefaultStdErrLoggingSystem(new TextStreamOutputEventListener(get(OutputEventListener.class)), get(TimeProvider.class));
+            return new DefaultLoggingManagerFactory(
+                    new DefaultLoggingConfigurer(renderer,
+                            new LogbackLoggingConfigurer(renderer),
+                            new JavaUtilLoggingConfigurer()),
+                    renderer,
+                    stdout,
+                    stderr);
         }
     }
 
-    private LoggingSystem getStdErrLoggingSystem() {
-        return get(StdErrLoggingSystem.class);
+    private static class CommandLineLogging extends ChildProcessLogging {
+        protected OutputEventRenderer createOutputEventRenderer() {
+            OutputEventRenderer renderer = new OutputEventRenderer(new ConsoleConfigureAction());
+            renderer.addStandardOutputAndError();
+            return renderer;
+        }
     }
 
-    private LoggingSystem getStdOutLoggingSystem() {
-        return get(StdOutLoggingSystem.class);
+    private static class EmbeddedLogging extends LoggingServiceRegistry {
+        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
+            OutputEventRenderer renderer = get(OutputEventRenderer.class);
+            // Configure logback only
+            return new DefaultLoggingManagerFactory(
+                    new DefaultLoggingConfigurer(renderer,
+                            new LogbackLoggingConfigurer(renderer)),
+                    renderer,
+                    new NoOpLoggingSystem(),
+                    new NoOpLoggingSystem());
+        }
     }
 
-    protected OutputEventRenderer createOutputEventRenderer() {
-        TerminalDetector terminalDetector;
-        if (detectConsole) {
-            StartParameter startParameter = new StartParameter();
-            JnaBootPathConfigurer jnaConfigurer = new JnaBootPathConfigurer(startParameter.getGradleUserHomeDir());
-            terminalDetector = new TerminalDetectorFactory().create(jnaConfigurer);
-        } else {
-            terminalDetector = new NoOpTerminalDetector();
+    private static class NestedLogging extends LoggingServiceRegistry {
+        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
+            OutputEventRenderer renderer = get(OutputEventRenderer.class);
+            // Don't configure anything
+            return new DefaultLoggingManagerFactory(renderer,
+                    renderer,
+                    new NoOpLoggingSystem(),
+                    new NoOpLoggingSystem());
         }
-        return new OutputEventRenderer(terminalDetector).addStandardOutputAndError();
     }
 }
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 2a46a76..c783ad1 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,14 +20,11 @@ import org.apache.commons.lang.StringUtils;
 import org.fusesource.jansi.Ansi;
 import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.SystemProperties;
 
 import java.io.Flushable;
 import java.io.IOException;
 
 public class AnsiConsole implements Console {
-    private final static String EOL = SystemProperties.getLineSeparator();
-
     private final Appendable target;
     private final Flushable flushable;
     private LabelImpl statusBar;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java
index b246c43..3204c12 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleBackedProgressRenderer.java
@@ -23,11 +23,13 @@ public class ConsoleBackedProgressRenderer implements OutputEventListener {
     private final OutputEventListener listener;
     private final Console console;
     private final LinkedList<Operation> operations = new LinkedList<Operation>();
+    private final StatusBarFormatter statusBarFormatter;
     private Label statusBar;
 
-    public ConsoleBackedProgressRenderer(OutputEventListener listener, Console console) {
+    public ConsoleBackedProgressRenderer(OutputEventListener listener, Console console, StatusBarFormatter statusBarFormatter) {
         this.listener = listener;
         this.console = console;
+        this.statusBarFormatter = statusBarFormatter;
     }
 
     public void onOutput(OutputEvent event) {
@@ -47,25 +49,13 @@ public class ConsoleBackedProgressRenderer implements OutputEventListener {
     }
 
     private void updateText() {
-        StringBuilder builder = new StringBuilder();
-        for (Operation operation : operations) {
-            String message = operation.getMessage();
-            if (message == null) {
-                continue;
-            }
-            if (builder.length() > 0) {
-                builder.append(' ');
-            }
-            builder.append("> ");
-            builder.append(message);
-        }
         if (statusBar == null) {
             statusBar = console.getStatusBar();
         }
-        statusBar.setText(builder.toString());
+        statusBar.setText(statusBarFormatter.format(operations));
     }
 
-    private static class Operation {
+    static class Operation {
         private final String shortDescription;
         private String status;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleConfigureAction.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleConfigureAction.java
new file mode 100644
index 0000000..dfdc7a4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ConsoleConfigureAction.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.logging.internal;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData;
+import org.gradle.internal.nativeplatform.console.ConsoleDetector;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+
+import java.io.PrintStream;
+
+public class ConsoleConfigureAction implements Action<OutputEventRenderer> {
+    public void execute(OutputEventRenderer renderer) {
+        StartParameter startParameter = new StartParameter();
+        NativeServices.initialize(startParameter.getGradleUserHomeDir());
+        ConsoleDetector consoleDetector = NativeServices.getInstance().get(ConsoleDetector.class);
+        ConsoleMetaData consoleMetaData = consoleDetector.getConsole();
+        if (consoleMetaData == null) {
+            return;
+        }
+        boolean stdOutIsTerminal = consoleMetaData.isStdOut();
+        boolean stdErrIsTerminal = consoleMetaData.isStdErr();
+        if (stdOutIsTerminal) {
+            PrintStream outStr = new PrintStream(org.fusesource.jansi.AnsiConsole.wrapOutputStream(renderer.getOriginalStdOut()));
+            Console console = new AnsiConsole(outStr, outStr, renderer.getColourMap());
+            renderer.addConsole(console, true, stdErrIsTerminal, consoleMetaData);
+        } else if (stdErrIsTerminal) {
+            // Only stderr is connected to a terminal
+            PrintStream errStr = new PrintStream(org.fusesource.jansi.AnsiConsole.wrapOutputStream(renderer.getOriginalStdErr()));
+            Console console = new AnsiConsole(errStr, errStr, renderer.getColourMap());
+            renderer.addConsole(console, false, true, consoleMetaData);
+        }
+    }
+}
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 a608b33..82c22e2 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
@@ -32,10 +32,6 @@ public class DefaultLoggingConfigurer implements LoggingConfigurer {
         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);
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 f5a2510..3745344 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
@@ -66,7 +66,7 @@ public class DefaultLoggingManager implements LoggingManagerInternal {
 
     public DefaultLoggingManager stop() {
         try {
-            new CompositeStoppable(loggingSystem, stdOutLoggingSystem, stdErrLoggingSystem).stop();
+            CompositeStoppable.stoppable(loggingSystem, stdOutLoggingSystem, stdErrLoggingSystem).stop();
             for (StandardOutputListener stdoutListener : stdoutListeners) {
                 loggingOutput.removeStandardOutputListener(stdoutListener);
             }
@@ -145,8 +145,12 @@ public class DefaultLoggingManager implements LoggingManagerInternal {
         }
     }
 
-    public void colorStdOutAndStdErr(boolean colorOutput) {
-        loggingOutput.colorStdOutAndStdErr(colorOutput);
+    public void attachConsole(boolean colorOutput) {
+        loggingOutput.attachConsole(colorOutput);
+    }
+
+    public void addStandardOutputAndError() {
+        loggingOutput.addStandardOutputAndError();
     }
 
     private static class StartableLoggingSystem implements Stoppable {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
index 6888cbc..cb62bf3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
@@ -111,7 +111,7 @@ public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
             assertStarted();
             assertNotCompleted();
             state = State.completed;
-            listener.completed(new ProgressCompleteEvent(timeProvider.getCurrentTime(), category, toStatus(status)));
+            listener.completed(new ProgressCompleteEvent(timeProvider.getCurrentTime(), category, description, toStatus(status)));
         }
 
         private String toStatus(String status) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirector.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirector.java
index 4a47107..a3f205a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirector.java
@@ -29,8 +29,8 @@ public class DefaultStandardOutputRedirector implements StandardOutputRedirector
     private PrintStream originalStdErr;
     private final WriteAction stdOut = new WriteAction();
     private final WriteAction stdErr = new WriteAction();
-    private final PrintStream redirectedStdOut = new LinePerThreadBufferingOutputStream(stdOut, true);
-    private final PrintStream redirectedStdErr = new LinePerThreadBufferingOutputStream(stdErr, true);
+    private final PrintStream redirectedStdOut = new LinePerThreadBufferingOutputStream(stdOut);
+    private final PrintStream redirectedStdErr = new LinePerThreadBufferingOutputStream(stdErr);
 
     public void redirectStandardOutputTo(StandardOutputListener stdOutDestination) {
         stdOut.setDestination(stdOutDestination);
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStatusBarFormatter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStatusBarFormatter.java
new file mode 100644
index 0000000..5a64f9c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStatusBarFormatter.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.logging.internal;
+
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData;
+
+import java.util.List;
+
+public class DefaultStatusBarFormatter implements StatusBarFormatter {
+    private final ConsoleMetaData consoleMetaData;
+
+    public DefaultStatusBarFormatter(ConsoleMetaData consoleMetaData) {
+        this.consoleMetaData = consoleMetaData;
+    }
+
+    public String format(List<ConsoleBackedProgressRenderer.Operation> operations) {
+        StringBuilder builder = new StringBuilder();
+        for (ConsoleBackedProgressRenderer.Operation operation : operations) {
+            String message = operation.getMessage();
+            if (message == null) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append(' ');
+            }
+            builder.append("> ");
+            builder.append(message);
+        }
+        return trim(builder);
+    }
+
+    private String trim(StringBuilder formattedString) {
+        // Don't write to the right-most column, as on some consoles the cursor will wrap to the next line
+        int width = consoleMetaData.getCols() - 1;
+        if (width > 0 && width < formattedString.length()) {
+            return formattedString.substring(0, width);
+        }
+        return formattedString.toString();
+    }
+}
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
deleted file mode 100644
index 33f26ca..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingManagerFactory.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.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/LoggingBackedStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
index 7e9d8bf..5f0fd2c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
@@ -36,7 +36,7 @@ public class LoggingBackedStyledTextOutput extends AbstractStyledTextOutput {
     private boolean styleChange;
 
     public LoggingBackedStyledTextOutput(OutputEventListener listener, String category, LogLevel logLevel, TimeProvider timeProvider) {
-        outstr = new LineBufferingOutputStream(new LogAction(listener, category, logLevel, timeProvider), true);
+        outstr = new LineBufferingOutputStream(new LogAction(listener, category, logLevel, timeProvider));
     }
 
     protected void doStyleChange(Style style) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingOutputInternal.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingOutputInternal.java
index e2c91f2..b82c628 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingOutputInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingOutputInternal.java
@@ -18,7 +18,15 @@ package org.gradle.logging.internal;
 import org.gradle.api.logging.LoggingOutput;
 
 public interface LoggingOutputInternal extends LoggingOutput {
-    void colorStdOutAndStdErr(boolean colorOutput);
+    /**
+     * Add standard output and error as logging destinations.
+     */
+    void addStandardOutputAndError();
+
+    /**
+     * Adds the console as logging destination, if available.
+     */
+    void attachConsole(boolean colorOutput);
 
     void addOutputEventListener(OutputEventListener listener);
 
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 b9a9541..18f913a 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
@@ -15,71 +15,69 @@
  */
 package org.gradle.logging.internal;
 
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.internal.nativeplatform.TerminalDetector;
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData;
 import org.gradle.listener.ListenerBroadcast;
 
-import java.io.FileDescriptor;
-import java.io.PrintStream;
+import java.io.OutputStream;
 
 /**
  * A {@link org.gradle.logging.internal.OutputEventListener} implementation which renders output events to various
  * destinations. This implementation is thread-safe.
  */
+ at ThreadSafe
 public class OutputEventRenderer implements OutputEventListener, LoggingConfigurer, LoggingOutputInternal {
     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 TerminalDetector terminalDetector;
     private final Object lock = new Object();
     private final DefaultColorMap colourMap = new DefaultColorMap();
     private LogLevel logLevel = LogLevel.LIFECYCLE;
+    private final Action<? super OutputEventRenderer> consoleConfigureAction;
+    private OutputStream originalStdOut;
+    private OutputStream originalStdErr;
+    private StreamBackedStandardOutputListener stdOutListener;
+    private StreamBackedStandardOutputListener stdErrListener;
 
-    public OutputEventRenderer(TerminalDetector terminalDetector) {
+    public OutputEventRenderer(Action<? super OutputEventRenderer> consoleConfigureAction) {
         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));
         formatters.add(stdErrChain);
-        this.terminalDetector = terminalDetector;
+        this.consoleConfigureAction = consoleConfigureAction;
     }
 
-    public void colorStdOutAndStdErr(boolean colorOutput) {
-        synchronized (lock) {
-            colourMap.setUseColor(colorOutput);
-        }
+    public ColorMap getColourMap() {
+        return colourMap;
     }
 
-    public OutputEventRenderer addStandardOutputAndError() {
-        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);
-            addConsole(console, true, stdErrIsTerminal);
-        } else if (stdErrIsTerminal) {
-            // Only stderr is connected to a terminal
-            PrintStream errStr = org.fusesource.jansi.AnsiConsole.err();
-            Console console = new AnsiConsole(errStr, errStr, colourMap);
-            addConsole(console, false, true);
-        }
-        if (!stdOutIsTerminal) {
-            addStandardOutput(System.out);
-        }
-        if (!stdErrIsTerminal) {
-            addStandardError(System.err);
-        }
-        return this;
+    public OutputStream getOriginalStdOut() {
+        return originalStdOut;
     }
 
-    public OutputEventRenderer addStandardOutput(final Appendable out) {
-        addStandardOutputListener(new StreamBackedStandardOutputListener(out));
-        return this;
+    public OutputStream getOriginalStdErr() {
+        return originalStdErr;
     }
 
-    public OutputEventRenderer addStandardError(final Appendable err) {
-        addStandardErrorListener(new StreamBackedStandardOutputListener(err));
-        return this;
+    public void attachConsole(boolean colorOutput) {
+        synchronized (lock) {
+            colourMap.setUseColor(colorOutput);
+            consoleConfigureAction.execute(this);
+        }
+    }
+
+    public void addStandardOutputAndError() {
+        synchronized (lock) {
+            originalStdOut = System.out;
+            originalStdErr = System.err;
+            stdOutListener = new StreamBackedStandardOutputListener((Appendable) System.out);
+            stdErrListener = new StreamBackedStandardOutputListener((Appendable) System.err);
+            addStandardOutputListener(stdOutListener);
+            addStandardErrorListener(stdErrListener);
+        }
     }
 
     public void addOutputEventListener(OutputEventListener listener) {
@@ -90,16 +88,25 @@ public class OutputEventRenderer implements OutputEventListener, LoggingConfigur
         formatters.remove(listener);
     }
 
-    public OutputEventRenderer addConsole(final Console console, boolean stdout, boolean stderr) {
-        final OutputEventListener consoleChain = new ConsoleBackedProgressRenderer(new ProgressLogEventGenerator(new StyledTextOutputBackedRenderer(console.getMainArea()), true), console);
+    public OutputEventRenderer addConsole(Console console, boolean stdout, boolean stderr, ConsoleMetaData consoleMetaData) {
+        final OutputEventListener consoleChain = new ConsoleBackedProgressRenderer(
+                new ProgressLogEventGenerator(
+                        new StyledTextOutputBackedRenderer(console.getMainArea()), true),
+                console,
+                new DefaultStatusBarFormatter(consoleMetaData));
         synchronized (lock) {
             if (stdout && stderr) {
                 formatters.add(consoleChain);
+                stdoutListeners.remove(this.stdOutListener);
+                stderrListeners.remove(this.stdErrListener);
             } else if (stdout) {
                 formatters.add(onNonError(consoleChain));
+                stdoutListeners.remove(this.stdOutListener);
             } else {
                 formatters.add(onError(consoleChain));
+                stderrListeners.remove(this.stdErrListener);
             }
+            consoleChain.onOutput(new LogLevelChangeEvent(logLevel));
         }
         return this;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
index dbafd35..6a7e15e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
@@ -35,7 +35,7 @@ abstract class PrintStreamLoggingSystem implements LoggingSystem {
         public void execute(String output) {
             destination.get().onOutput(output);
         }
-    }, true);
+    });
     private StandardOutputListener original;
     private LogLevel logLevel;
     private final StandardOutputListener listener;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressCompleteEvent.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressCompleteEvent.java
index a8082fd..c2d2bd0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressCompleteEvent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressCompleteEvent.java
@@ -19,16 +19,22 @@ import org.gradle.api.logging.LogLevel;
 
 public class ProgressCompleteEvent extends CategorisedOutputEvent {
     private final String status;
+    private final String description;
 
-    public ProgressCompleteEvent(long timestamp, String category, String status) {
+    public ProgressCompleteEvent(long timestamp, String category, String description, String status) {
         super(timestamp, category, LogLevel.LIFECYCLE);
         this.status = status;
+        this.description = description;
     }
 
     public String getStatus() {
         return status;
     }
 
+    public String getDescription() {
+        return description;
+    }
+
     @Override
     public String toString() {
         return String.format("ProgressComplete %s", status);
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StatusBarFormatter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StatusBarFormatter.java
new file mode 100644
index 0000000..312dde1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StatusBarFormatter.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.logging.internal;
+
+import java.util.List;
+
+public interface StatusBarFormatter {
+    String format(List<ConsoleBackedProgressRenderer.Operation> operations);
+}
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
deleted file mode 100755
index 75468a4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.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.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.debug("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/logback/SimpleLogbackLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/SimpleLogbackLoggingConfigurer.java
deleted file mode 100644
index 6b0adee..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/SimpleLogbackLoggingConfigurer.java
+++ /dev/null
@@ -1,42 +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.logging.internal.logback;
-
-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 Logback configurer meant to be used in embedded mode
- * in 'safe' environment (e.g. own class loader).
- *
- * by Szczepan Faber, created at: 1/23/12
- */
-public class SimpleLogbackLoggingConfigurer implements LoggingConfigurer {
-    private final OutputEventRenderer renderer = new OutputEventRenderer(new NoOpTerminalDetector());
-    private final LoggingConfigurer configurer = new LogbackLoggingConfigurer(renderer);
-
-    public SimpleLogbackLoggingConfigurer() {
-        renderer.addStandardOutputAndError();
-    }
-
-    public void configure(LogLevel logLevel) {
-        renderer.configure(logLevel);
-        configurer.configure(logLevel);
-    }
-}
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 3323502..7cab12c 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,6 +17,8 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.ObjectConnection;
@@ -29,6 +31,7 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 public class DefaultWorkerProcess implements WorkerProcess {
+    private final static Logger LOGGER = Logging.getLogger(DefaultWorkerProcess.class);
     private final Lock lock = new ReentrantLock();
     private final Condition condition = lock.newCondition();
     private ObjectConnection connection;
@@ -64,6 +67,7 @@ public class DefaultWorkerProcess implements WorkerProcess {
     private void onConnect(ObjectConnection connection) {
         lock.lock();
         try {
+            LOGGER.debug("Received connection {} from {}", connection, execHandle);
             this.connection = connection;
             condition.signalAll();
         } finally {
@@ -88,7 +92,10 @@ public class DefaultWorkerProcess implements WorkerProcess {
 
     @Override
     public String toString() {
-        return execHandle.toString();
+        return "DefaultWorkerProcess{"
+                + "running=" + running
+                + ", execHandle=" + execHandle
+                + '}';
     }
 
     public ObjectConnection getConnection() {
@@ -111,7 +118,7 @@ public class DefaultWorkerProcess implements WorkerProcess {
             while (connection == null && running) {
                 try {
                     if (!condition.awaitUntil(connectExpiry)) {
-                        throw new ExecException(String.format("Timeout after waiting %.1f seconds for %s to connect.", ((double) connectTimeout) / 1000, execHandle));
+                        throw new ExecException(String.format("Timeout after waiting %.1f seconds for %s (%s, running: %s) to connect.", ((double) connectTimeout) / 1000, execHandle, execHandle.getState(), running));
                     }
                 } catch (InterruptedException e) {
                     throw UncheckedException.throwAsUncheckedException(e);
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 64f21e3..50367b6 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
@@ -16,10 +16,11 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.JavaForkOptions;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GUtil;
 
 import java.io.File;
@@ -48,7 +49,7 @@ public class JavaExecHandleBuilder extends AbstractExecHandleBuilder implements
         allArgs.addAll(javaOptions.getAllJvmArgs());
         if (!classpath.isEmpty()) {
             allArgs.add("-cp");
-            allArgs.add(GUtil.join(classpath.getFiles(), File.pathSeparator));
+            allArgs.add(CollectionUtils.join(File.pathSeparator, classpath.getFiles()));
         }
         return allArgs;
     }
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
index 31e77d0..91ae121 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
@@ -20,6 +20,7 @@ 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.GUtil;
 import org.gradle.util.internal.ArgumentsSplitter;
 
 import java.io.File;
@@ -30,24 +31,25 @@ 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 static final String FILE_ENCODING_KEY = "file.encoding";
+    private static final String JMX_REMOTE_KEY = "com.sun.management.jmxremote";
 
     private final List<Object> extraJvmArgs = new ArrayList<Object>();
     private final Map<String, Object> systemProperties = new TreeMap<String, Object>();
+    private final Map<String, Object> immutableSystemProperties = 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);
+        immutableSystemProperties.put(FILE_ENCODING_KEY, Charset.defaultCharset().name());
     }
 
     /**
@@ -55,13 +57,7 @@ public class JvmOptions {
      */
     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()));
-            }
-        }
+        formatSystemProperties(getSystemProperties(), args);
 
         // 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)
@@ -70,6 +66,16 @@ public class JvmOptions {
         return args;
     }
 
+    private void formatSystemProperties(Map<String, ?> properties, List<String> args) {
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            if (entry.getValue() != null && entry.getValue().toString().length() > 0) {
+                args.add(String.format("-D%s=%s", entry.getKey(), entry.getValue().toString()));
+            } else {
+                args.add(String.format("-D%s", entry.getKey()));
+            }
+        }
+    }
+
     /**
      * @return all immutable jvm args. It excludes most system properties.
      * Only implicitly immutable system properties like "file.encoding" are included.
@@ -99,9 +105,9 @@ public class JvmOptions {
             args.add(String.format("-Xbootclasspath:%s", bootstrapClasspath.getAsPath()));
         }
 
-        // This is implemented as a system property, but doesn't really function like one
+        // These are implemented as a system property, but don't really function like one
         // So we include it in this “no system property” set.
-        addDefaultEncodingJvmArg(args);
+        formatSystemProperties(immutableSystemProperties, args);
 
         if (assertionsEnabled) {
             args.add("-ea");
@@ -119,6 +125,7 @@ public class JvmOptions {
         maxHeapSize = null;
         extraJvmArgs.clear();
         assertionsEnabled = false;
+        debug = false;
         jvmArgs(arguments);
     }
 
@@ -139,19 +146,14 @@ public class JvmOptions {
         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);
+            Matcher matcher = SYS_PROP_PATTERN.matcher(argStr);
             if (matcher.matches()) {
-                systemProperties.put(matcher.group(1), matcher.group(2));
+                systemProperty(matcher.group(1), matcher.group(2));
                 continue;
             }
             matcher = NO_ARG_SYS_PROP_PATTERN.matcher(argStr);
             if (matcher.matches()) {
-                systemProperties.put(matcher.group(1), "");
+                systemProperty(matcher.group(1), "");
                 continue;
             }
             matcher = MIN_HEAP_PATTERN.matcher(argStr);
@@ -166,7 +168,7 @@ public class JvmOptions {
             }
             matcher = BOOTSTRAP_PATTERN.matcher(argStr);
             if (matcher.matches()) {
-                setBootstrapClasspath(matcher.group(1).split(Pattern.quote(File.pathSeparator)));
+                setBootstrapClasspath((Object[])matcher.group(1).split(Pattern.quote(File.pathSeparator)));
                 continue;
             }
             if (argStr.equals("-ea") || argStr.equals("-enableassertions")) {
@@ -196,8 +198,6 @@ public class JvmOptions {
         if (xdebugFound && xrunjdwpFound) {
             debug = true;
             extraJvmArgs.removeAll(matches);
-        } else {
-            debug = false;
         }
     }
 
@@ -215,16 +215,14 @@ public class JvmOptions {
     }
 
     public void systemProperties(Map<String, ?> properties) {
-        final Object fileEncoding = properties.remove(FILE_ENCODING_KEY);
-        if (fileEncoding != null) {
-            defaultCharacterEncoding = fileEncoding.toString();
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            systemProperty(entry.getKey(), entry.getValue());
         }
-        systemProperties.putAll(properties);
     }
 
     public void systemProperty(String name, Object value) {
-        if (name.equals(FILE_ENCODING_KEY)) {
-            defaultCharacterEncoding = value.toString();
+        if (name.equals(FILE_ENCODING_KEY) || name.equals(JMX_REMOTE_KEY)) {
+            immutableSystemProperties.put(name, value);
         } else {
             systemProperties.put(name, value);
         }
@@ -263,26 +261,11 @@ public class JvmOptions {
     }
 
     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()));
+        return immutableSystemProperties.get(FILE_ENCODING_KEY).toString();
     }
 
     public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
-        this.defaultCharacterEncoding = defaultCharacterEncoding;
+        immutableSystemProperties.put(FILE_ENCODING_KEY, GUtil.isTrue(defaultCharacterEncoding) ? defaultCharacterEncoding : Charset.defaultCharset().name());
     }
 
     public boolean getEnableAssertions() {
@@ -309,7 +292,7 @@ public class JvmOptions {
         target.setBootstrapClasspath(bootstrapClasspath);
         target.setEnableAssertions(assertionsEnabled);
         target.setDebug(debug);
-        target.setDefaultCharacterEncoding(defaultCharacterEncoding);
+        target.systemProperties(immutableSystemProperties);
     }
 
     public static List<String> fromString(String 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
index 6d05dfc..aba64ee 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
@@ -16,10 +16,10 @@
 
 package org.gradle.process.internal;
 
-import org.gradle.internal.concurrent.Synchronizer;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.Synchronizer;
 import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
 import org.gradle.internal.os.OperatingSystem;
 
@@ -65,7 +65,6 @@ public class ProcessParentingInitializer {
         if (initialized) {
             return operation.create();
         }
-        //TODO SF the interface can be deleted.
         return synchronizer.synchronize(new Factory<T>() {
             public T create() {
                 if (initialized) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java
index 07cc1e6..358e50a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java
@@ -30,18 +30,16 @@ import java.util.Set;
 /**
  * <p>A builder which configures and creates a {@link WorkerProcess} instance.</p>
  *
- * <p>A worker process is specified using an {@link Action}. The given action instance is serialized across into the
- * worker process and executed.</p>
+ * <p>A worker process is specified using an {@link Action}. The given action instance is serialized across into the worker process and executed.</p>
  *
- * <p>A worker process can optionally specify an application classpath. The classes of this classpath are loaded into an
- * isolated ClassLoader, which is made visible to the worker action ClassLoader. Only the packages specified in the set
- * of shared packages are visible to the worker action ClassLoader.</p>
+ * <p>A worker process can optionally specify an application classpath. The classes of this classpath are loaded into an isolated ClassLoader, which is made visible to the worker action ClassLoader.
+ * Only the packages specified in the set of shared packages are visible to the worker action ClassLoader.</p>
  */
 public abstract class WorkerProcessBuilder {
     private final JavaExecHandleBuilder javaCommand;
     private final Set<String> packages = new HashSet<String>();
     private final Set<File> applicationClasspath = new LinkedHashSet<File>();
-    private Action<WorkerProcessContext> action;
+    private Action<? super WorkerProcessContext> action;
     private LogLevel logLevel = LogLevel.LIFECYCLE;
     private boolean loadApplicationInSystemClassLoader;
 
@@ -72,12 +70,12 @@ public abstract class WorkerProcessBuilder {
         return packages;
     }
 
-    public WorkerProcessBuilder worker(Action<WorkerProcessContext> action) {
+    public WorkerProcessBuilder worker(Action<? super WorkerProcessContext> action) {
         this.action = action;
         return this;
     }
 
-    public Action<WorkerProcessContext> getWorker() {
+    public Action<? super WorkerProcessContext> getWorker() {
         return action;
     }
 
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 f5f4ef7..968cd50 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
@@ -34,12 +34,12 @@ import java.io.Serializable;
  */
 public class ActionExecutionWorker implements Action<WorkerContext>, Serializable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ActionExecutionWorker.class);
-    private final Action<WorkerProcessContext> action;
+    private final Action<? super WorkerProcessContext> action;
     private final Object workerId;
     private final String displayName;
     private final Address serverAddress;
 
-    public ActionExecutionWorker(Action<WorkerProcessContext> action, Object workerId, String displayName,
+    public ActionExecutionWorker(Action<? super WorkerProcessContext> action, Object workerId, String displayName,
                                  Address serverAddress) {
         this.action = action;
         this.workerId = workerId;
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 ca09880..a482c8c 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
@@ -82,7 +82,7 @@ public class ImplementationClassLoaderWorker implements Action<WorkerContext>, S
     }
 
     LoggingManagerInternal createLoggingManager() {
-        return LoggingServiceRegistry.newChildProcessLogging().newInstance(LoggingManagerInternal.class);
+        return LoggingServiceRegistry.newProcessLogging().newInstance(LoggingManagerInternal.class);
     }
 
     MutableURLClassLoader createImplementationClassLoader(ClassLoader system, ClassLoader application) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java
index 00bb5f2..e1fb0ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java
@@ -52,7 +52,7 @@ public class ExecOutputHandleRunner implements Runnable {
                 outputStream.write(buffer, 0, nread);
                 outputStream.flush();
             }
-            new CompositeStoppable(inputStream, outputStream).stop();
+            CompositeStoppable.stoppable(inputStream, outputStream).stop();
         } catch (Throwable t) {
             LOGGER.error(String.format("Could not %s.", displayName), t);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
index aeef3ca..c2c4303 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
@@ -15,9 +15,11 @@
  */
 package org.gradle.reporting;
 
-import org.gradle.internal.UncheckedException;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.IoActions;
 
-import java.io.*;
+import java.io.File;
+import java.io.Writer;
 
 public abstract class TextReportRenderer<T> {
     /**
@@ -28,17 +30,12 @@ public abstract class TextReportRenderer<T> {
     /**
      * 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 {
+    public void writeTo(final T model, File file) {
+        IoActions.writeFile(file, "utf-8", new ErroringAction<Writer>() {
+            @Override
+            protected void doExecute(Writer writer) throws Exception {
                 writeTo(model, writer);
-            } finally {
-                writer.close();
             }
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
+        });
     }
 }
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 696db00..168bc85 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,10 +15,10 @@
  */
 package org.gradle.testfixtures.internal;
 
+import org.gradle.api.internal.project.GlobalServicesRegistry;
 import org.gradle.internal.Factory;
 import org.gradle.internal.TrueTimeProvider;
 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;
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 5ebcbba..f703397 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
@@ -76,6 +76,9 @@ public class NoOpLoggingManager implements LoggingManagerInternal {
     public void removeOutputEventListener(OutputEventListener listener) {
     }
 
-    public void colorStdOutAndStdErr(boolean colorOutput) {
+    public void attachConsole(boolean colorOutput) {
+    }
+
+    public void addStandardOutputAndError() {
     }
 }
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 bfc3716..09bc272 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,11 +17,11 @@ package org.gradle.testfixtures.internal;
 
 import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleDistributionLocator;
-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;
+import org.gradle.internal.service.ServiceRegistry;
 
 import java.io.File;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java b/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
index af37faa..fd7efdd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
@@ -43,6 +43,7 @@ public class AvailablePortFinder {
     private final Lock lock = new ReentrantLock();
     private final int startPort;
     private int current;
+    private static final AvailablePortFinder INSTANCE = new AvailablePortFinder();
 
     /**
      * Creates a port finder that operates on private ports.
@@ -50,10 +51,10 @@ public class AvailablePortFinder {
      * @return a port finder that operates on private ports
      */
     public static AvailablePortFinder createPrivate() {
-        return new AvailablePortFinder();
+        return INSTANCE;
     }
 
-    public AvailablePortFinder() {
+    private AvailablePortFinder() {
         startPort = new Random().nextInt(MAX_PRIVATE_PORT - MIN_PRIVATE_PORT) + MIN_PRIVATE_PORT;
         current = startPort;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
index 078007f..317cff3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
@@ -48,16 +48,8 @@ public class Clock {
     }
 
     public String getTime() {
-        StringBuffer result = new StringBuffer();
         long timeInMs = getTimeInMs();
-        if (timeInMs > MS_PER_HOUR) {
-            result.append(timeInMs / MS_PER_HOUR).append(" hrs ");
-        }
-        if (timeInMs > MS_PER_MINUTE) {
-            result.append((timeInMs % MS_PER_HOUR) / MS_PER_MINUTE).append(" mins ");
-        }
-        result.append((timeInMs % MS_PER_MINUTE) / 1000.0).append(" secs");
-        return result.toString();
+        return prettyTime(timeInMs);
     }
 
     public long getTimeInMs() {
@@ -71,4 +63,16 @@ public class Clock {
     public long getStartTime() {
         return start;
     }
+
+    public static String prettyTime(long timeInMs) {
+        StringBuffer result = new StringBuffer();
+        if (timeInMs > MS_PER_HOUR) {
+            result.append(timeInMs / MS_PER_HOUR).append(" hrs ");
+        }
+        if (timeInMs > MS_PER_MINUTE) {
+            result.append((timeInMs % MS_PER_HOUR) / MS_PER_MINUTE).append(" mins ");
+        }
+        result.append((timeInMs % MS_PER_MINUTE) / 1000.0).append(" secs");
+        return result.toString();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
deleted file mode 100644
index 6d7c5d6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
+++ /dev/null
@@ -1,107 +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 com.google.common.collect.Lists;
-import org.gradle.api.Transformer;
-import org.gradle.api.specs.Spec;
-
-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;
-    }
-    
-    public static <E> List<E> compact(List<E> list) {
-        boolean foundAtLeastOneNull = false;
-        List<E> compacted = null;
-        int i = 0;
-        
-        for (E element : list) {
-            if (element == null) {
-                if (!foundAtLeastOneNull) {
-                    compacted = new ArrayList<E>(list.size());
-                    if (i > 0) {
-                        compacted.addAll(list.subList(0, i));
-                    }
-                }
-                foundAtLeastOneNull = true;
-            } else if (foundAtLeastOneNull) {
-                compacted.add(element);
-            }
-            ++i;
-        }
-
-        return foundAtLeastOneNull ? compacted : list;
-    }
-
-    public static <C extends Collection<String>> C stringize(Iterable<?> source, C destination) {
-        return collect(source, destination, new ToStringTransformer());
-    }
-
-    public static List<String> stringize(List<?> source) {
-        return stringize(source, new ArrayList<String>(source.size()));
-    }
-
-}
\ 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 aaa4013..85b78d8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -18,6 +18,7 @@ package org.gradle.util;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingMethodException;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.DynamicObjectUtil;
 
@@ -125,23 +126,8 @@ public class ConfigureUtil {
     }
 
     private static <T> T configure(Closure configureClosure, T delegate, int resolveStrategy, boolean configureableAware) {
-        if (configureClosure == null) {
-            return delegate;
-        }
-
-        if (configureableAware && delegate instanceof Configurable) {
-            ((Configurable)delegate).configure(configureClosure);
-        } else {
-            Closure copy = (Closure) configureClosure.clone();
-            copy.setResolveStrategy(resolveStrategy);
-            copy.setDelegate(delegate);
-            if (copy.getMaximumNumberOfParameters() == 0) {
-                copy.call();
-            } else {
-                copy.call(delegate);
-            }
-        }
-
+        ClosureBackedAction<T> action = new ClosureBackedAction<T>(configureClosure, resolveStrategy, configureableAware);
+        action.execute(delegate);
         return delegate;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
index 4dcc075..0823741 100755
--- a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
@@ -24,6 +24,8 @@ import org.slf4j.LoggerFactory;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public class DeprecationLogger {
     private static final Logger LOGGER = LoggerFactory.getLogger(DeprecationLogger.class);
@@ -50,6 +52,35 @@ public class DeprecationLogger {
 
     public static final String ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME = "org.gradle.deprecation.trace";
 
+    private static String deprecationMessage;
+    private static Lock deprecationMessageLock = new ReentrantLock();
+
+    private static String getDeprecationMessage() {
+        if (deprecationMessage == null) {
+            deprecationMessageLock.lock();
+            try {
+                if (deprecationMessage == null) {
+                    String messageBase = "has been deprecated and is scheduled to be removed in";
+                    String when;
+
+                    GradleVersion currentVersion = GradleVersion.current();
+                    int versionMajor = currentVersion.getMajor();
+                    if (versionMajor == -1) { // don't understand version number
+                        when = "the next major version of Gradle";
+                    } else {
+                        when = String.format("Gradle %d.0", versionMajor + 1);
+                    }
+
+                    deprecationMessage = String.format("%s %s", messageBase, when);
+                }
+            } finally {
+                deprecationMessageLock.unlock();
+            }
+        }
+
+        return deprecationMessage;
+    }
+
     public static void reset() {
         PLUGINS.clear();
         METHODS.clear();
@@ -61,17 +92,17 @@ public class DeprecationLogger {
     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));
+                    "The %s plugin %S. Please use the %s plugin instead.",
+                    pluginName, getDeprecationMessage(), replacement));
             logTraceIfNecessary();
         }
     }
 
-    public static void nagUserOfReplacedTask(String taskName, String replacement) {
+    public static void nagUserOfReplacedTaskType(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));
+                    "The %s task type %s. Please use the %s instead.",
+                    taskName, getDeprecationMessage(), replacement));
             logTraceIfNecessary();
         }
     }
@@ -79,8 +110,8 @@ public class DeprecationLogger {
     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));
+                    "The %s method %s. Please use the %s method instead.",
+                    methodName, getDeprecationMessage(), replacement));
             logTraceIfNecessary();
         }
     }
@@ -88,24 +119,24 @@ public class DeprecationLogger {
     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));
+                    "The %s property %s. Please use the %s property instead.",
+                    propertyName, getDeprecationMessage(), 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));
+            LOGGER.warn(String.format("The %s method %s.",
+                    methodName, getDeprecationMessage()));
             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));
+            LOGGER.warn(String.format("The %s property %s. %s",
+                    propertyName, getDeprecationMessage(), advice));
             logTraceIfNecessary();
         }
     }
@@ -113,19 +144,37 @@ public class DeprecationLogger {
     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));
+                    "The %s named parameter %s. Please use the %s named parameter instead.",
+                    parameterName, getDeprecationMessage(), replacement));
             logTraceIfNecessary();
         }
     }
 
+    /**
+     * Try to avoid using this nagging method. The other methods use a consistent wording for when things will be removed.
+     */
     public static void nagUserWith(String message) {
         if (isEnabled() && METHODS.add(message)) {
             LOGGER.warn(message);
             logTraceIfNecessary();
         }
     }
-    
+
+    /**
+     * Avoid using this method, use the variant with an explanation instead.
+     */
+    public static void nagUserOfDeprecated(String thing) {
+        nagUserWith(String.format("%s %s", thing, getDeprecationMessage()));
+    }
+
+    public static void nagUserOfDeprecated(String thing, String explanation) {
+        nagUserWith(String.format("%s %s. %s.", thing, getDeprecationMessage(), explanation));
+    }
+
+    public static void nagUserOfDeprecatedBehaviour(String behaviour) {
+        nagUserOfDeprecated(String.format("%s. This behaviour", behaviour));
+    }
+
     public static <T> T whileDisabled(Factory<T> factory) {
         ENABLED.set(false);
         try {
@@ -135,6 +184,15 @@ public class DeprecationLogger {
         }
     }
 
+    public static void whileDisabled(Runnable action) {
+        ENABLED.set(false);
+        try {
+            action.run();
+        } finally {
+            ENABLED.set(true);
+        }
+    }
+
     private static boolean isTraceLoggingEnabled() {
         return Boolean.getBoolean(ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME) || LOG_TRACE.get();
     }
@@ -162,7 +220,7 @@ public class DeprecationLogger {
         if (!isEnabled()) {
             return;
         }
-        nagUserWith("Dynamic properties are deprecated: http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html");
+        nagUserOfDeprecated("Creating properties on demand (a.k.a. dynamic properties)", "Please read http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html for information on the replacement for dynamic properties");
 
         String propertyWithClass = target.getClass().getName() + "." + propertyName;
         if (DYNAMIC_PROPERTIES.add(propertyWithClass)) {
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 d75d988..b3ea7a2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
@@ -15,9 +15,8 @@
  */
 package org.gradle.util;
 
+import org.gradle.api.Action;
 import org.gradle.internal.UncheckedException;
-import org.gradle.internal.concurrent.DefaultExecutorFactory;
-import org.gradle.internal.concurrent.ExecutorFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -38,23 +37,35 @@ public class DisconnectableInputStream extends BulkReadInputStream {
     private boolean closed;
     private boolean inputFinished;
 
+    /*
+        The song and dance with Action<Runnable> is to ease testing.
+        See DisconnectableInputStreamTest
+     */
+
+    static class ThreadExecuter implements Action<Runnable> {
+        public void execute(Runnable runnable) {
+            Thread thread = new Thread(runnable);
+            thread.setName("DisconnectableInputStream source reader");
+            thread.setDaemon(true);
+            thread.start();
+        }
+    }
+
     public DisconnectableInputStream(InputStream source) {
-        // We use our own executor factory, as there is no way to break a worker thread out of source.read() below
-        this(source, new DefaultExecutorFactory(), 1024);
+        this(source, 1024);
     }
 
-    public DisconnectableInputStream(InputStream source, int bufferLength) {
-        // We use our own executor factory, as there is no way to break a worker thread out of source.read() below
-        this(source, new DefaultExecutorFactory(), bufferLength);
+    public DisconnectableInputStream(final InputStream source, int bufferLength) {
+        this(source, new ThreadExecuter(), bufferLength);
     }
 
-    DisconnectableInputStream(InputStream source, ExecutorFactory executorFactory) {
-        this(source, executorFactory, 1024);
+    DisconnectableInputStream(InputStream source, Action<Runnable> executer) {
+        this(source, executer, 1024);
     }
 
-    DisconnectableInputStream(final InputStream source, ExecutorFactory executorFactory, int bufferLength) {
+    DisconnectableInputStream(final InputStream source, Action<Runnable> executer, int bufferLength) {
         buffer = new byte[bufferLength];
-        executorFactory.create("read input").execute(new Runnable() {
+        Runnable consume = new Runnable() {
             public void run() {
                 try {
                     while (true) {
@@ -93,7 +104,7 @@ public class DisconnectableInputStream extends BulkReadInputStream {
                                 assert buffer.length >= writePos;
                                 condition.signalAll();
                             }
-                            if (closed || nread < 0) {
+                            if (nread < 0) {
                                 // End of the stream
                                 inputFinished = true;
                                 condition.signalAll();
@@ -114,41 +125,44 @@ public class DisconnectableInputStream extends BulkReadInputStream {
                     throw UncheckedException.throwAsUncheckedException(throwable);
                 }
             }
-        });
+        };
+
+        executer.execute(consume);
     }
 
     @Override
     public int read(byte[] bytes, int pos, int count) throws IOException {
         lock.lock();
-        int nread;
         try {
             while (!inputFinished && !closed && readPos == writePos) {
                 condition.await();
             }
-            if (inputFinished && readPos == writePos) {
-                return -1;
-            }
             if (closed) {
                 return -1;
             }
-            assert writePos > readPos;
-            nread = Math.min(count, writePos - readPos);
-            System.arraycopy(buffer, readPos, bytes, pos, nread);
-            readPos += nread;
-            assert writePos >= readPos;
-            condition.signalAll();
+
+            // Drain the buffer before returning end-of-stream
+            if (writePos > readPos) {
+                int nread = Math.min(count, writePos - readPos);
+                System.arraycopy(buffer, readPos, bytes, pos, nread);
+                readPos += nread;
+                assert writePos >= readPos;
+                condition.signalAll();
+                return nread;
+            }
+
+            assert inputFinished;
+            return -1;
         } catch (InterruptedException e) {
             throw UncheckedException.throwAsUncheckedException(e);
         } finally {
             lock.unlock();
         }
-        return nread;
     }
 
     /**
-     * Closes this {@code InputStream} for reading. Any threads blocked in read() will receive a {@link
-     * java.nio.channels.AsynchronousCloseException}. Also requests that the reader thread stop reading, if possible,
-     * but does not block waiting for this to happen.
+     * Closes this {@code InputStream} for reading. Any threads blocked in read() will receive an end-of-stream. Also requests that the
+     * reader thread stop reading, if possible, but does not block waiting for this to happen.
      *
      * <p>NOTE: this method does not close the source input stream.</p>
      */
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 fd26344..810db05 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -19,6 +19,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.filefilter.IOFileFilter;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.internal.LimitedDescription;
 
 import java.io.*;
@@ -26,9 +27,7 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 import java.util.zip.Checksum;
 
 /**
@@ -52,6 +51,29 @@ public class GFileUtils {
         }
     }
 
+    public static void moveFile(File source, File destination) {
+        try {
+            FileUtils.moveFile(source, destination);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static void copyFile(File source, File destination) {
+        try {
+            FileUtils.copyFile(source, destination);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static void moveDirectory(File source, File destination) {
+        try {
+            FileUtils.moveDirectory(source, destination);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
     public static String readFile(File file) {
         return readFile(file, Charset.defaultCharset().name());
     }
@@ -120,6 +142,14 @@ public class GFileUtils {
         }
     }
 
+    public static void cleanDirectory(File directory) {
+        try {
+            FileUtils.cleanDirectory(directory);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
     public static boolean deleteQuietly(File file) {
         return FileUtils.deleteQuietly(file);
     }
@@ -194,8 +224,79 @@ public class GFileUtils {
      * UncheckedIOException if it fails to do so.
      */
     public static void createDirectory(File directory) {
-        if (!directory.exists() && !directory.mkdirs()) {
+        if (!directory.mkdirs() && !directory.isDirectory()) {
             throw new UncheckedIOException("Failed to create directory " + directory);
         }
     }
+
+    /**
+     * Returns a relative path from 'from' to 'to'
+     *
+     * @param from where to calculate from
+     * @param to where to calculate to
+     * @return The relative path
+     */
+    public static String relativePath(File from, File to) {
+        try {
+            return org.apache.tools.ant.util.FileUtils.getRelativePath(from, to);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    /**
+     * Makes the parent directory of the file, and any non existent parents.
+     *
+     * @param child The file to create the parent dir for
+     * @return The parent dir file
+     * @see #mkdirs(java.io.File)
+     */
+    public static File parentMkdirs(File child) {
+        File parent = child.getParentFile();
+        mkdirs(parent);
+        return parent;
+    }
+
+    /**
+     * Like {@link java.io.File#mkdirs()}, except throws an informative error if a dir cannot be created.
+     *
+     * @param dir The dir to create, including any non existent parent dirs.
+     */
+    public static void mkdirs(File dir) {
+        dir = dir.getAbsoluteFile();
+        if (dir.isDirectory()) {
+            return;
+        }
+
+        if (dir.exists() && !dir.isDirectory()) {
+            throw new UncheckedIOException(String.format("Cannot create directory '%s' as it already exists, but is not a directory", dir));
+        }
+
+        List<File> toCreate = new LinkedList<File>();
+        File parent = dir.getParentFile();
+        while (!parent.exists()) {
+            toCreate.add(parent);
+            parent = parent.getParentFile();
+        }
+
+        Collections.reverse(toCreate);
+        for (File parentDirToCreate : toCreate) {
+            if (parentDirToCreate.isDirectory()) {
+                continue;
+            }
+
+            File parentDirToCreateParent = parentDirToCreate.getParentFile();
+            if (!parentDirToCreateParent.isDirectory()) {
+                throw new UncheckedIOException(String.format("Cannot create parent directory '%s' when creating directory '%s' as '%s' is not a directory", parentDirToCreate, dir, parentDirToCreateParent));
+            }
+
+            if (!parentDirToCreate.mkdir()) {
+                throw new UncheckedIOException(String.format("Failed to create parent directory '%s' when creating directory '%s'", parentDirToCreate, dir));
+            }
+        }
+
+        if (!dir.mkdirs()) {
+            throw new UncheckedIOException(String.format("Failed to create directory '%s'", dir));
+        }
+    }
 }
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 e064ae5..e1be0fa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
@@ -105,27 +105,8 @@ public class GUtil {
         return flatten(elements, new ArrayList());
     }
 
-    public static String join(Collection self, String separator) {
-        StringBuilder buffer = new StringBuilder();
-        boolean first = true;
-
-        if (separator == null) {
-            separator = "";
-        }
-
-        for (Object value : self) {
-            if (first) {
-                first = false;
-            } else {
-                buffer.append(separator);
-            }
-            buffer.append(value.toString());
-        }
-        return buffer.toString();
-    }
-
-    public static String join(Object[] self, String separator) {
-        return join(asList(self), separator);
+    public static String asPath(Iterable<?> collection) {
+        return CollectionUtils.join(File.pathSeparator, collection);
     }
 
     public static List<String> prefix(String prefix, Collection<String> strings) {
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 80350d6..84f3713 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -20,6 +20,7 @@ import groovy.lang.GroovySystem;
 import org.apache.ivy.Ivy;
 import org.apache.tools.ant.Main;
 import org.gradle.api.GradleException;
+import org.gradle.api.Nullable;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.jvm.Jvm;
@@ -161,6 +162,26 @@ public class GradleVersion implements Comparable<GradleVersion> {
         return versionPart == null || snapshot != null;
     }
 
+    /**
+     * The base version number of the overall version.
+     *
+     * For example, the version base of '1.2-rc-1' is '1.2'.
+     *
+     * @return The version base, or null if the version is unrecognised.
+     */
+    @Nullable
+    public String getVersionBase() {
+        return versionPart;
+    }
+
+    public int getMajor() {
+        if (isValid()) {
+            return Integer.valueOf(versionPart.split("\\.", 2)[0], 10);
+        } else {
+            return -1;
+        }
+    }
+
     private boolean isNonSymbolicNumber() {
         return versionPart.equals("0.0");
     }
@@ -266,6 +287,10 @@ public class GradleVersion implements Comparable<GradleVersion> {
         return sb.toString();
     }
 
+    public boolean isValid() {
+        return versionPart != null;
+    }
+
     static final class Stage implements Comparable<Stage> {
         final int stage;
         final int number;
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 a9f979d..3349792 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
@@ -31,7 +31,7 @@ public class Jvm implements JavaInfo {
     }
 
     public static Jvm current() {
-        DeprecationLogger.nagUserWith("The class org.gradle.util.Jvm has been deprecated and will be removed in the next version of Gradle.");
+        DeprecationLogger.nagUserOfDeprecated("The class org.gradle.util.Jvm");
         return new Jvm(org.gradle.internal.jvm.Jvm.current());
     }
 
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 cbe746a..2811d0c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
@@ -30,24 +30,16 @@ public class LineBufferingOutputStream extends OutputStream {
     private boolean hasBeenClosed;
     private final byte[] lineSeparator;
     private final int bufferIncrement;
+    private final Action<String> action;
     private byte[] buf;
     private int count;
-    private final Output output;
 
     public LineBufferingOutputStream(Action<String> action) {
-        this(action, false, 2048);
+        this(action, 2048);
     }
 
-    public LineBufferingOutputStream(Action<String> action, boolean includeEOL) {
-        this(action, includeEOL, 2048);
-    }
-
-    public LineBufferingOutputStream(Action<String> action, boolean includeEOL, int bufferLength) {
-        this(new StringOutput(includeEOL, action), bufferLength);
-    }
-
-    private LineBufferingOutputStream(Output output, int bufferLength) {
-        this.output = output;
+    public LineBufferingOutputStream(Action<String> action, int bufferLength) {
+        this.action = action;
         bufferIncrement = bufferLength;
         buf = new byte[bufferLength];
         count = 0;
@@ -114,11 +106,7 @@ public class LineBufferingOutputStream extends OutputStream {
      */
     public void flush() {
         if (count != 0) {
-            int length = count;
-            if (endsWithLineSeparator()) {
-                length -= lineSeparator.length;
-            }
-            output.write(buf, length, count);
+            action.execute(new String(buf, 0, count));
         }
         reset();
     }
@@ -129,26 +117,4 @@ public class LineBufferingOutputStream extends OutputStream {
         }
         count = 0;
     }
-
-    private interface Output {
-        void write(byte[] buffer, int textLength, int lineLength);
-    }
-
-    private static class StringOutput implements Output {
-        private final boolean includeEOL;
-        private final Action<String> action;
-
-        public StringOutput(boolean includeEOL, Action<String> action) {
-            this.includeEOL = includeEOL;
-            this.action = action;
-        }
-
-        public void write(byte[] buffer, int textLength, int lineLength) {
-            if (includeEOL) {
-                action.execute(new String(buffer, 0, lineLength));
-            } else {
-                action.execute(new String(buffer, 0, textLength));
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
index 1928b21..f848cd7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
@@ -27,26 +27,20 @@ import java.util.Locale;
 
 public class LinePerThreadBufferingOutputStream extends PrintStream {
     private final Action<String> listener;
-    private final boolean includeEol;
     private final ThreadLocal<PrintStream> stream = new ThreadLocal<PrintStream>(){
         @Override
         protected PrintStream initialValue() {
             return AccessController.doPrivileged(new PrivilegedAction<PrintStream>() {
                 public PrintStream run() {
-                    return new PrintStream(new LineBufferingOutputStream(listener, includeEol));
+                    return new PrintStream(new LineBufferingOutputStream(listener));
                 }
             });
         }
     };
 
     public LinePerThreadBufferingOutputStream(Action<String> listener) {
-        this(listener, false);
-    }
-
-    public LinePerThreadBufferingOutputStream(Action<String> listener, boolean includeEol) {
         super(new ByteArrayOutputStream(), true);
         this.listener = listener;
-        this.includeEol = includeEol;
     }
 
     private PrintStream getStream() {
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 14e4abf..a921af0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
@@ -19,6 +19,7 @@ package org.gradle.util;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.gradle.internal.SystemProperties;
 
+import java.io.File;
 import java.util.regex.Pattern;
 
 public class TextUtil {
@@ -60,6 +61,13 @@ public class TextUtil {
     }
 
     /**
+     * Converts all native file separators in the specified string to '/'.
+     */
+    public static String normaliseFileSeparators(String path) {
+        return path.replaceAll(Pattern.quote(File.separator), "/");
+    }
+
+    /**
      * <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>
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java b/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java
deleted file mode 100644
index 9bf5220..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.Transformer;
-
-public class ToStringTransformer implements Transformer<String, Object> {
-
-    public String transform(Object original) {
-        return original.toString();
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
new file mode 100644
index 0000000..c32214e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.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.util;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Ordering;
+
+import org.gradle.api.Nullable;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents, parses, and compares version numbers following a major.minor.micro-qualifier format.
+ */
+public class VersionNumber implements Comparable<VersionNumber> {
+    public static final VersionNumber UNKNOWN = new VersionNumber(0, 0, 0, null);
+
+    private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\.(\\d+))?+(?:\\.(\\d+))?+(?:[-\\.](.+))?");
+    private static final String VERSION_TEMPLATE = "%d.%d.%d%s";
+
+    private final int major;
+    private final int minor;
+    private final int micro;
+    private final String qualifier;
+
+    public VersionNumber(int major, int minor, int micro, @Nullable String qualifier) {
+        this.major = major;
+        this.minor = minor;
+        this.micro = micro;
+        this.qualifier = qualifier;
+    }
+
+    public int getMajor() {
+        return major;
+    }
+
+    public int getMinor() {
+        return minor;
+    }
+
+    public int getMicro() {
+        return micro;
+    }
+
+    public String getQualifier() {
+        return qualifier;
+    }
+
+    public int compareTo(VersionNumber other) {
+        if (major != other.major) { return major - other.major; }
+        if (minor != other.minor) { return minor - other.minor; }
+        if (micro != other.micro) { return micro - other.micro; }
+        return Ordering.natural().nullsFirst().compare(qualifier, other.qualifier);
+    }
+
+    public boolean equals(Object other) {
+        return other instanceof VersionNumber && compareTo((VersionNumber)other) == 0;
+    }
+
+    public int hashCode() {
+        int result = major;
+        result = 31 * result + minor;
+        result = 31 * result + micro;
+        result = 31 * result + Objects.hashCode(qualifier);
+        return result;
+    }
+
+    public String toString() {
+        return String.format(VERSION_TEMPLATE, major, minor, micro, qualifier == null ? "" : "-" + qualifier);
+    }
+
+    public static VersionNumber parse(String versionString) {
+        if (versionString == null) { return UNKNOWN; }
+        Matcher m = VERSION_PATTERN.matcher(versionString);
+        if (!m.matches()) { return UNKNOWN; }
+
+        int major = Integer.valueOf(m.group(1));
+        String minorString = m.group(2);
+        int minor = minorString == null ? 0 : Integer.valueOf(minorString);
+        String microString = m.group(3);
+        int micro = microString == null ? 0 : Integer.valueOf(microString);
+        String qualifier = m.group(4);
+
+        return new VersionNumber(major, minor, micro, qualifier);
+    }
+}
+
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 9dc6cfb..5924cf4 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
@@ -2,9 +2,13 @@ import org.gradle.*
 import org.gradle.util.*
 import org.gradle.api.*
 import org.gradle.api.artifacts.*
+import org.gradle.api.artifacts.result.*
 import org.gradle.api.artifacts.dsl.*
 import org.gradle.api.artifacts.maven.*
 import org.gradle.api.artifacts.specs.*
+import org.gradle.api.publish.*
+import org.gradle.api.publish.ivy.*
+import org.gradle.api.publish.ivy.tasks.*
 import org.gradle.api.execution.*
 import org.gradle.api.file.*
 import org.gradle.api.resources.*
@@ -18,6 +22,7 @@ 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.plugins.buildcomparison.gradle.*
 import org.gradle.api.specs.*
 import org.gradle.api.tasks.*
 import org.gradle.api.tasks.bundling.*
@@ -27,4 +32,5 @@ import org.gradle.api.tasks.javadoc.*
 import org.gradle.api.tasks.testing.*
 import org.gradle.api.tasks.util.*
 import org.gradle.api.tasks.wrapper.*
+import org.gradle.api.tasks.scala.*
 import org.gradle.process.*
diff --git a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
index 7a55941..faf0658 100644
--- a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
@@ -28,6 +28,7 @@ import org.gradle.logging.TestStyledTextOutput
 import org.gradle.util.TreeVisitor
 
 import spock.lang.Specification
+import org.gradle.execution.MultipleBuildFailures
 
 class BuildExceptionReporterTest extends Specification {
     final TestStyledTextOutput output = new TestStyledTextOutput()
@@ -228,6 +229,54 @@ Run {userinput}[gradle tasks]{normal} to get a list of available tasks.
 '''
     }
 
+    def reportsMultipleBuildFailures() {
+        def failure1 = exception("<location>", "<message>", new RuntimeException("<cause>"))
+        def failure2 = new GradleException("<failure>")
+        def failure3 = new RuntimeException("<error>")
+        Throwable exception = new MultipleBuildFailures([failure1, failure2, failure3])
+
+        expect:
+        reporter.buildFinished(result(exception))
+        output.value == '''
+{failure}FAILURE: Build completed with 3 failures.{normal}
+
+{failure}1: {normal}{failure}Task failed with an exception.{normal}
+-----------
+* Where:
+<location>
+
+* What went wrong:
+<message>
+{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.
+==============================================================================
+
+{failure}2: {normal}{failure}Task failed with an exception.{normal}
+-----------
+* What went wrong:
+<failure>
+
+* 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.
+==============================================================================
+
+{failure}3: {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://forums.gradle.org.
+
+* Try:
+Run with {userinput}--debug{normal} option to get additional debug info.
+
+* Exception is:
+java.lang.RuntimeException: <error>
+{stacktrace}
+==============================================================================
+''';
+    }
+
     def reportsBuildFailureWhenShowStacktraceEnabled() {
         configuration.showStacktrace = ShowStacktrace.ALWAYS
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy
new file mode 100644
index 0000000..b0c3a9a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Project
+import org.gradle.api.Task
+import org.gradle.api.invocation.Gradle
+import org.gradle.api.tasks.TaskState
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
+
+public class TaskExecutionLoggerTest extends Specification {
+
+    def progressLoggerFactory = Mock(ProgressLoggerFactory)
+    def task = Mock(Task)
+    def state = Mock(TaskState)
+    def progressLogger = Mock(ProgressLogger)
+    def gradle = Mock(Gradle)
+    def executionLogger = new TaskExecutionLogger(progressLoggerFactory);
+    def project = Mock(Project)
+
+    def setup() {
+        task.project >> project
+        project.gradle >> gradle
+        gradle.parent >> null
+        task.path >> ":path"
+    }
+
+    def "logs execution of task in root build"() {
+        when:
+        executionLogger.beforeExecute(task)
+
+        then:
+        interaction {
+            startLogTaskExecution(':path')
+        }
+
+        when:
+        executionLogger.afterExecute(task, state);
+
+        then:
+        1 * state.skipMessage >> null
+        1 * progressLogger.completed(null)
+    }
+
+    def "logs execution of task in sub build"() {
+        def rootProject = Mock(Project)
+
+
+        when:
+        executionLogger.beforeExecute(task)
+
+        then:
+        interaction {
+            _ * gradle.parent >> Mock(Gradle)
+            _ * gradle.rootProject >> rootProject
+            _ * rootProject.name >> "build"
+            startLogTaskExecution(':build:path')
+        }
+
+        when:
+        executionLogger.afterExecute(task, state);
+
+        then:
+        1 * state.skipMessage >> null
+        1 * progressLogger.completed(null)
+    }
+
+    def "logs skipped task execution"() {
+        when:
+        executionLogger.beforeExecute(task)
+
+        then:
+        interaction {
+            startLogTaskExecution(':path')
+        }
+
+        when:
+        executionLogger.afterExecute(task, state);
+
+        then:
+        1 * state.skipMessage >> "skipped"
+        1 * progressLogger.completed("skipped")
+    }
+
+    def startLogTaskExecution(def path) {
+        1 * progressLoggerFactory.newOperation(TaskExecutionLogger) >> progressLogger
+        1 * progressLogger.setDescription("Execute " + path)
+        1 * progressLogger.setShortDescription("$path")
+        1 * progressLogger.setLoggingHeader("$path")
+        1 * progressLogger.started()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java b/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java
deleted file mode 100644
index 733cd74..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java
+++ /dev/null
@@ -1,128 +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;
-
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.api.tasks.TaskState;
-import org.gradle.logging.ProgressLoggerFactory;
-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;
-
- at RunWith(JMock.class)
-public class TaskExecutionLoggerTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory.class);
-    private final Task task = context.mock(Task.class);
-    private final TaskState state = context.mock(TaskState.class);
-    private final ProgressLogger progressLogger = context.mock(ProgressLogger.class);
-    private final Gradle gradle = context.mock(Gradle.class);
-    private final TaskExecutionLogger executionLogger = new TaskExecutionLogger(progressLoggerFactory);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            Project project = context.mock(Project.class);
-            allowing(task).getProject();
-            will(returnValue(project));
-            allowing(project).getGradle();
-            will(returnValue(gradle));
-            allowing(task).getPath();
-            will(returnValue(":path"));
-        }});
-    }
-
-    @Test
-    public void logsExecutionOfTaskInRootBuild() {
-        context.checking(new Expectations() {{
-            allowing(gradle).getParent();
-            will(returnValue(null));
-            one(progressLoggerFactory).newOperation(TaskExecutionLogger.class);
-            will(returnValue(progressLogger));
-            one(progressLogger).setDescription("Execute :path");
-            one(progressLogger).setShortDescription(":path");
-            one(progressLogger).setLoggingHeader(":path");
-            one(progressLogger).started();
-        }});
-
-        executionLogger.beforeExecute(task);
-
-        context.checking(new Expectations() {{
-            allowing(state).getSkipMessage();
-            will(returnValue(null));
-            one(progressLogger).completed(null);
-        }});
-
-        executionLogger.afterExecute(task, state);
-    }
-
-    @Test
-    public void logsExecutionOfTaskInSubBuild() {
-        context.checking(new Expectations() {{
-            Project rootProject = context.mock(Project.class, "rootProject");
-
-            allowing(gradle).getParent();
-            will(returnValue(context.mock(Gradle.class, "rootBuild")));
-            allowing(gradle).getRootProject();
-            will(returnValue(rootProject));
-            allowing(rootProject).getName();
-            will(returnValue("build"));
-
-            one(progressLoggerFactory).newOperation(TaskExecutionLogger.class);
-            will(returnValue(progressLogger));
-            one(progressLogger).setDescription("Execute :build:path");
-            one(progressLogger).setShortDescription(":build:path");
-            one(progressLogger).setLoggingHeader(":build:path");
-            one(progressLogger).started();
-        }});
-
-        executionLogger.beforeExecute(task);
-
-        context.checking(new Expectations() {{
-            allowing(state).getSkipMessage();
-            will(returnValue(null));
-            one(progressLogger).completed(null);
-        }});
-
-        executionLogger.afterExecute(task, state);
-    }
-
-    @Test
-    public void logsSkippedTaskExecution() {
-        context.checking(new Expectations() {{
-            allowing(gradle).getParent();
-            will(returnValue(null));
-
-            one(progressLoggerFactory).newOperation(TaskExecutionLogger.class);
-            will(returnValue(progressLogger));
-            allowing(state).getSkipMessage();
-            will(returnValue("skipped"));
-            one(progressLogger).completed("skipped");
-
-            ignoring(progressLogger);
-        }});
-
-        executionLogger.beforeExecute(task);
-        executionLogger.afterExecute(task, state);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorGroovyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorGroovyTest.groovy
new file mode 100644
index 0000000..5523da1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorGroovyTest.groovy
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.reflect.DirectInstantiator
+import spock.lang.Specification
+import spock.lang.Issue
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.Action
+
+class AsmBackedClassGeneratorGroovyTest extends Specification {
+
+    def generator = new AsmBackedClassGenerator()
+    def instantiator = new ClassGeneratorBackedInstantiator(generator, new DirectInstantiator())
+
+    private <T> T create(Class<T> clazz, Object... args) {
+        instantiator.newInstance(clazz, *args)
+    }
+
+    @Issue("GRADLE-2417")
+    def "can use dynamic object as closure delegate"() {
+        given:
+        def thing = create(DynamicThing)
+
+        when:
+        conf(thing) {
+            m1(1,2,3)
+            p1 = 1
+            p1 = p1 + 1
+        }
+
+        then:
+        thing.methods.size() == 1
+        thing.props.p1 == 2
+    }
+
+    def "unassociated missing exceptions are thrown"() {
+        given:
+        def thing1 = create(DynamicThing)
+
+        when:
+        thing1.onMethodMissing = { name, args -> [].foo() }
+        conf(thing1) { m1() }
+
+        then:
+        def e = thrown(groovy.lang.MissingMethodException)
+        e.method == "foo"
+
+        when:
+        thing1.onPropertyMissingGet = { new Object().bar }
+        conf(thing1) { abc }
+
+        then:
+        e = thrown(groovy.lang.MissingPropertyException)
+        e.property == "bar"
+
+        when:
+        thing1.onPropertyMissingSet = { name, value -> new Object().baz = true }
+        conf(thing1) { abc = true }
+
+        then:
+        e = thrown(groovy.lang.MissingPropertyException)
+        e.property == "baz"
+
+    }
+
+    def "any method with action as the last param is closurised"() {
+        given:
+        def tester = create(ActionsTester)
+
+        when:
+        tester.oneAction { assert it == "subject" }
+
+        then:
+        tester.lastMethod == "oneAction"
+        tester.lastArgs.size() == 1
+        tester.lastArgs.first() instanceof ClosureBackedAction
+
+        when:
+        tester.twoArgs("1") { assert it == "subject" }
+
+        then:
+        tester.lastMethod == "twoArgs"
+        tester.lastArgs.size() == 2
+        tester.lastArgs.first() == "1"
+        tester.lastArgs.last() instanceof ClosureBackedAction
+
+        when:
+        tester.threeArgs("1", "2") { assert it == "subject" }
+
+        then:
+        tester.lastMethod == "threeArgs"
+        tester.lastArgs.size() == 3
+        tester.lastArgs.first() == "1"
+        tester.lastArgs[1] == "2"
+        tester.lastArgs.last() instanceof ClosureBackedAction
+
+        when:
+        tester.overloaded("1") { assert it == "subject" }
+
+        then:
+        tester.lastMethod == "overloaded"
+        tester.lastArgs.size() == 2
+        tester.lastArgs.first() == "1"
+        tester.lastArgs.last() instanceof ClosureBackedAction
+
+        when:
+        tester.overloaded(1) { assert it == "subject" }
+
+        then:
+        tester.lastMethod == "overloaded"
+        tester.lastArgs.size() == 2
+        tester.lastArgs.first() == 1
+        tester.lastArgs.last() instanceof ClosureBackedAction
+
+        when:
+        def closure = { assert it == "subject" }
+        tester.hasClosure("1", closure)
+
+        then:
+        tester.lastMethod == "hasClosure"
+        tester.lastArgs.size() == 2
+        tester.lastArgs.first() == "1"
+        tester.lastArgs.last().is(closure)
+    }
+
+    def conf(o, c) {
+        ConfigureUtil.configure(c, o)
+    }
+}
+
+class DynamicThing {
+    def methods = [:]
+    def props = [:]
+
+    Closure onMethodMissing = { name, args -> methods[name] = args.toList() }
+    Closure onPropertyMissingGet = { name -> props[name] }
+    Closure onPropertyMissingSet = { name, value -> props[name] = value }
+
+    def methodMissing(String name, args) {
+        onMethodMissing(name, args)
+    }
+
+    def propertyMissing(String name) {
+        onPropertyMissingGet(name)
+    }
+
+    def propertyMissing(String name, value) {
+        onPropertyMissingSet(name, value)
+    }
+}
+
+class ActionsTester {
+
+    Object subject = "subject"
+    String lastMethod
+    List lastArgs
+
+    void oneAction(Action action) {
+        lastMethod = "oneAction"
+        lastArgs = [action]
+        action.execute(subject)
+    }
+
+    void twoArgs(String first, Action action) {
+        lastMethod = "twoArgs"
+        lastArgs = [first, action]
+        action.execute(subject)
+    }
+
+    void threeArgs(String first, String second, Action action) {
+        lastMethod = "threeArgs"
+        lastArgs = [first, second, action]
+        action.execute(subject)
+    }
+
+    void overloaded(Integer i, Action action) {
+        lastMethod = "overloaded"
+        lastArgs = [i, action]
+        action.execute(subject)
+    }
+
+    void overloaded(String s, Action action) {
+        lastMethod = "overloaded"
+        lastArgs = [s, action]
+        action.execute(subject)
+    }
+
+    void hasClosure(String s, Action action) {
+        lastMethod = "hasClosure"
+        lastArgs = [s, action]
+    }
+
+    void hasClosure(String s, Closure closure) {
+        lastMethod = "hasClosure"
+        lastArgs = [s, closure]
+    }
+
+
+
+}
\ No newline at end of file
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 082c537..7e86531 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
@@ -19,14 +19,19 @@ import groovy.lang.Closure;
 import groovy.lang.GroovyObject;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
+import org.gradle.api.JavaVersion;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtensionAware;
 import org.gradle.api.plugins.ExtensionContainer;
+import org.gradle.internal.reflect.ObjectInstantiationException;
 import org.junit.Test;
 import spock.lang.Issue;
 
+import javax.inject.Inject;
+import java.io.IOException;
+import java.lang.reflect.*;
 import java.util.*;
 import java.util.concurrent.Callable;
 
@@ -112,6 +117,163 @@ public class AsmBackedClassGeneratorTest {
     }
 
     @Test
+    public void includesGenericTypeInformationForOverriddenConstructor() throws Exception {
+        Class<?> generatedClass = generator.generate(BeanWithComplexConstructor.class);
+        Constructor<?> constructor = generatedClass.getDeclaredConstructors()[0];
+
+        assertThat(constructor.getTypeParameters().length, equalTo(3));
+
+        assertThat(constructor.getGenericParameterTypes().length, equalTo(12));
+
+        // Callable
+        Type paramType = constructor.getGenericParameterTypes()[0];
+        assertThat(paramType, equalTo((Type) Callable.class));
+
+        // Callable<String>
+        paramType = constructor.getGenericParameterTypes()[1];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        ParameterizedType parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], equalTo((Type) String.class));
+
+        // Callable<? extends String>
+        paramType = constructor.getGenericParameterTypes()[2];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        WildcardType wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getUpperBounds()[0], equalTo((Type)String.class));
+        assertThat(wildcard.getLowerBounds().length, equalTo(0));
+
+        // Callable<? super String>
+        paramType = constructor.getGenericParameterTypes()[3];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getUpperBounds()[0], equalTo((Type)Object.class));
+        assertThat(wildcard.getLowerBounds().length, equalTo(1));
+        assertThat(wildcard.getLowerBounds()[0], equalTo((Type)String.class));
+
+        // Callable<?>
+        paramType = constructor.getGenericParameterTypes()[4];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getUpperBounds()[0], equalTo((Type)Object.class));
+        assertThat(wildcard.getLowerBounds().length, equalTo(0));
+
+        // Callable<? extends Callable<?>>
+        paramType = constructor.getGenericParameterTypes()[5];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getLowerBounds().length, equalTo(0));
+        assertThat(wildcard.getUpperBounds()[0], instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) wildcard.getUpperBounds()[0];
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getUpperBounds()[0], equalTo((Type) Object.class));
+        assertThat(wildcard.getLowerBounds().length, equalTo(0));
+
+        // Callable<S>
+        paramType = constructor.getGenericParameterTypes()[6];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(TypeVariable.class));
+        TypeVariable typeVariable = (TypeVariable) parameterizedType.getActualTypeArguments()[0];
+        assertThat(typeVariable.getName(), equalTo("S"));
+        assertThat(typeVariable.getBounds()[0], instanceOf(ParameterizedType.class));
+
+        // Callable<? extends T>
+        paramType = constructor.getGenericParameterTypes()[7];
+        assertThat(paramType, instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) paramType;
+        assertThat(parameterizedType.getRawType(), equalTo((Type) Callable.class));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+        wildcard = (WildcardType) parameterizedType.getActualTypeArguments()[0];
+        assertThat(wildcard.getUpperBounds().length, equalTo(1));
+        assertThat(wildcard.getLowerBounds().length, equalTo(0));
+        assertThat(wildcard.getUpperBounds()[0], instanceOf(TypeVariable.class));
+        typeVariable = (TypeVariable) wildcard.getUpperBounds()[0];
+        assertThat(typeVariable.getName(), equalTo("T"));
+        assertThat(typeVariable.getBounds()[0], equalTo((Type) IOException.class));
+
+        // V
+        paramType = constructor.getGenericParameterTypes()[8];
+        assertThat(paramType, instanceOf(TypeVariable.class));
+        typeVariable = (TypeVariable) paramType;
+        assertThat(typeVariable.getName(), equalTo("V"));
+        assertThat(typeVariable.getBounds()[0], equalTo((Type) Object.class));
+
+        GenericArrayType arrayType;
+
+        // String[]
+        paramType = constructor.getGenericParameterTypes()[9];
+
+        /*
+            Java 7 fixed http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5041784
+            The net effect is that non generic arrays are incorrectly encoded as generic
+            array types pre Java 7 and correctly encoded as plain object arrays Java 7 and up.
+         */
+        if (JavaVersion.current().isJava7Compatible()) {
+            assertThat(paramType, equalTo((Type) String[].class));
+            assertThat(((Class<?>) paramType).getComponentType(), equalTo((Type) String.class));
+        } else {
+            assertThat(paramType, instanceOf(GenericArrayType.class));
+            arrayType = (GenericArrayType) paramType;
+            assertThat(arrayType.getGenericComponentType(), equalTo((Type) String.class));
+        }
+
+        // List<? extends String>[]
+        paramType = constructor.getGenericParameterTypes()[10];
+        assertThat(paramType, instanceOf(GenericArrayType.class));
+        arrayType = (GenericArrayType) paramType;
+        assertThat(arrayType.getGenericComponentType(), instanceOf(ParameterizedType.class));
+        parameterizedType = (ParameterizedType) arrayType.getGenericComponentType();
+        assertThat(parameterizedType.getRawType(), equalTo((Type) List.class));
+        assertThat(parameterizedType.getActualTypeArguments().length, equalTo(1));
+        assertThat(parameterizedType.getActualTypeArguments()[0], instanceOf(WildcardType.class));
+
+        // boolean
+        paramType = constructor.getGenericParameterTypes()[11];
+        assertThat(paramType, equalTo((Type) Boolean.TYPE));
+
+        assertThat(constructor.getGenericExceptionTypes().length, equalTo(2));
+
+        // throws Exception
+        Type exceptionType = constructor.getGenericExceptionTypes()[0];
+        assertThat(exceptionType, equalTo((Type) Exception.class));
+
+        // throws T
+        exceptionType = constructor.getGenericExceptionTypes()[1];
+        assertThat(exceptionType, instanceOf(TypeVariable.class));
+        typeVariable = (TypeVariable) exceptionType;
+        assertThat(typeVariable.getName(), equalTo("T"));
+    }
+
+    @Test
+    public void includesAnnotationInformationForOverriddenConstructor() throws Exception {
+        Class<?> generatedClass = generator.generate(BeanWithAnnotatedConstructor.class);
+        Constructor<?> constructor = generatedClass.getDeclaredConstructors()[0];
+
+        assertThat(constructor.getAnnotation(Inject.class), notNullValue());
+    }
+
+    @Test
     public void canConstructInstance() throws Exception {
         Bean bean = generator.newInstance(BeanWithConstructor.class, "value");
         assertThat(bean.getClass(), sameInstance((Object) generator.generate(BeanWithConstructor.class)));
@@ -129,14 +291,14 @@ public class AsmBackedClassGeneratorTest {
         try {
             generator.newInstance(UnconstructableBean.class);
             fail();
-        } catch (UnsupportedOperationException e) {
-            assertThat(e, sameInstance(UnconstructableBean.failure));
+        } catch (ObjectInstantiationException e) {
+            assertThat(e.getCause(), sameInstance(UnconstructableBean.failure));
         }
 
         try {
             generator.newInstance(Bean.class, "arg1", 2);
             fail();
-        } catch (IllegalArgumentException e) {
+        } catch (ObjectInstantiationException e) {
             // expected
         }
 
@@ -554,6 +716,30 @@ public class AsmBackedClassGeneratorTest {
         }
     }
 
+    public static class BeanWithComplexConstructor {
+        public <T extends IOException, S extends Callable<String>, V> BeanWithComplexConstructor(
+                Callable rawValue,
+                Callable<String> value,
+                Callable<? extends String> subType,
+                Callable<? super String> superType,
+                Callable<?> wildcard,
+                Callable<? extends Callable<?>> nested,
+                Callable<S> typeVar,
+                Callable<? extends T> typeVarWithBounds,
+                V genericVar,
+                String[] array,
+                List<? extends String>[] genericArray,
+                boolean primitive
+        ) throws Exception, T {
+        }
+    }
+
+    public static class BeanWithAnnotatedConstructor {
+        @Inject
+        public BeanWithAnnotatedConstructor() {
+        }
+    }
+
     public static class BeanWithDslMethods extends Bean {
         private String prop;
         private FileCollection files;
@@ -778,9 +964,9 @@ public class AsmBackedClassGeneratorTest {
     }
 
     public static class UnconstructableBean {
-        static UnsupportedOperationException failure = new UnsupportedOperationException();
+        static Throwable failure = new UnsupportedOperationException();
 
-        public UnconstructableBean() {
+        public UnconstructableBean() throws Throwable {
             throw failure;
         }
     }
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 2dc58d0..a90bd70 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,7 +16,6 @@
 
 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
@@ -26,6 +25,9 @@ import org.gradle.listener.ListenerManager
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
+
+import java.util.concurrent.Callable
+
 import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
@@ -40,7 +42,6 @@ class DefaultTaskTest extends AbstractTaskTest {
     Object testCustomPropValue;
 
     @Before public void setUp() {
-        super.setUp()
         testCustomPropValue = new Object()
         defaultTask = createTask(DefaultTask.class)
         cl = Thread.currentThread().contextClassLoader
@@ -80,8 +81,8 @@ class DefaultTaskTest extends AbstractTaskTest {
 
     @Test
     public void testDoFirstAddsActionToTheStartOfActionsList() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
+        Action<Task> action1 = Actions.doNothing();
+        Action<Task> action2 = Actions.doNothing();
 
         assertSame(defaultTask, defaultTask.doFirst(action1));
         assertEquals(1, defaultTask.actions.size());
@@ -95,8 +96,8 @@ class DefaultTaskTest extends AbstractTaskTest {
 
     @Test
     public void testDoLastAddsActionToTheEndOfActionsList() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
+        Action<Task> action1 = Actions.doNothing();
+        Action<Task> action2 = Actions.doNothing();
 
         assertSame(defaultTask, defaultTask.doLast(action1));
         assertEquals(1, defaultTask.actions.size());
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyInjectingInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyInjectingInstantiatorTest.groovy
new file mode 100644
index 0000000..8c6080d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyInjectingInstantiatorTest.groovy
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.service.ServiceRegistry
+import javax.inject.Inject
+import org.gradle.api.Action
+import org.gradle.internal.reflect.ObjectInstantiationException
+import org.gradle.internal.service.UnknownServiceException
+
+class DependencyInjectingInstantiatorTest extends Specification {
+    final ServiceRegistry services = Mock()
+    final Action warning = Mock()
+    final DependencyInjectingInstantiator instantiator = new DependencyInjectingInstantiator(services, warning)
+
+    def "creates instance that has default constructor"() {
+        when:
+        def result = instantiator.newInstance(HasDefaultConstructor)
+
+        then:
+        result != null
+        0 * warning._
+    }
+
+    def "injects provided parameters into constructor"() {
+        when:
+        def result = instantiator.newInstance(HasInjectConstructor, "string", 12)
+
+        then:
+        result.param1 == "string"
+        result.param2 == 12
+        0 * warning._
+    }
+
+    def "injects missing parameters from provided service registry"() {
+        given:
+        _ * services.get(String) >> "string"
+
+        when:
+        def result = instantiator.newInstance(HasInjectConstructor, 12)
+
+        then:
+        result.param1 == "string"
+        result.param2 == 12
+        0 * warning._
+    }
+
+    def "unboxes primitive types"() {
+        when:
+        def result = instantiator.newInstance(AcceptsPrimitiveTypes, 12, true)
+
+        then:
+        result.param1 == 12
+        result.param2
+        0 * warning._
+    }
+
+    def "constructors do not need to be public"() {
+        expect:
+        instantiator.newInstance(HasPrivateConstructor, "param") != null
+    }
+
+    def "prefers exact match constructor when class has multiple constructors and none are annotated"() {
+        when:
+        def result = instantiator.newInstance(HasNoInjectConstructor, "param")
+
+        then:
+        result != null
+        1 * warning.execute("Class $HasNoInjectConstructor.name has multiple constructors and no constructor is annotated with @Inject. In Gradle 2.0 this will be treated as an error.")
+        0 * warning._
+    }
+
+    def "prefers exact match constructor when class has multiple constructors and another is annotated"() {
+        when:
+        def result = instantiator.newInstance(HasOneInjectConstructor, "param")
+
+        then:
+        result != null
+        1 * warning.execute("Class $HasOneInjectConstructor.name has @Inject annotation on an unexpected constructor. In Gradle 2.0 the constructor annotated with @Inject will be used instead of the current default constructor.")
+        0 * warning._
+    }
+
+    def "prefers exact match constructor when class has multiple annotated constructors"() {
+        when:
+        def result = instantiator.newInstance(HasMultipleInjectConstructors, "param")
+
+        then:
+        result != null
+        1 * warning.execute("Class $HasMultipleInjectConstructors.name has multiple constructors with @Inject annotation. In Gradle 2.0 this will be treated as an error.")
+        0 * warning._
+    }
+
+    def "warns when class has exactly one constructor that takes parameters parameters and is not annotated"() {
+        when:
+        def result = instantiator.newInstance(HasNonAnnotatedConstructor, "param")
+
+        then:
+        result != null
+        1 * warning.execute("Constructor for class $HasNonAnnotatedConstructor.name is not annotated with @Inject. In Gradle 2.0 this will be treated as an error.")
+        0 * warning._
+
+        when:
+        result = instantiator.newInstance(HasNonAnnotatedConstructor)
+
+        then:
+        result != null
+        1 * warning.execute("Constructor for class $HasNonAnnotatedConstructor.name is not annotated with @Inject. In Gradle 2.0 this will be treated as an error.")
+        0 * warning._
+    }
+
+    def "does not warn when class has multiple constructors and exact match constructor is annotated"() {
+        when:
+        def result = instantiator.newInstance(HasOneInjectConstructor, 12)
+
+        then:
+        result != null
+        0 * warning._
+    }
+
+    def "does not warn when class has multiple constructors and there is no exact match and one constructor is annotated"() {
+        given:
+        _ * services.get(Number) >> 12
+
+        when:
+        def result = instantiator.newInstance(HasOneInjectConstructor)
+
+        then:
+        result != null
+        0 * warning._
+    }
+
+    def "propagates constructor failure"() {
+        when:
+        instantiator.newInstance(HasBrokenConstructor)
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause == HasBrokenConstructor.failure
+    }
+
+    def "fails when too many constructor parameters provided"() {
+        when:
+        instantiator.newInstance(HasOneInjectConstructor, 12, "param2")
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause.message == "Too many parameters provided for constructor for class $HasOneInjectConstructor.name. Expected 1, received 2."
+    }
+
+    def "fails when supplied parameters cannot be used to call constructor"() {
+        given:
+        _ * services.get(Number) >> 12
+
+        when:
+        instantiator.newInstance(HasOneInjectConstructor, new StringBuilder("string"))
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause.message == "Unexpected parameter provided for constructor for class $HasOneInjectConstructor.name."
+    }
+
+    def "handles missing service"() {
+        given:
+        def failure = new UnknownServiceException(String, "unknown")
+        _ * services.get(String) >> { throw failure }
+
+        when:
+        instantiator.newInstance(HasNonAnnotatedConstructor)
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause == failure
+    }
+
+    def "fails when class has multiple matching constructors"() {
+        when:
+        instantiator.newInstance(HasMultipleCompatibleConstructor, "param")
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause.message == "Class $HasMultipleCompatibleConstructor.name has multiple constructors that accept parameters [param]."
+    }
+
+    def "fails when class has no matching constructors and none are annotated"() {
+        when:
+        instantiator.newInstance(HasNoInjectConstructor, new StringBuilder("param"))
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause.message == "Class $HasNoInjectConstructor.name has no constructor that accepts parameters [param] or that is annotated with @Inject."
+    }
+
+    def "fails when class has no matching constructor and multiple are annotated"() {
+        when:
+        instantiator.newInstance(HasMultipleInjectConstructors, new StringBuilder("param"))
+
+        then:
+        ObjectInstantiationException e = thrown()
+        e.cause.message == "Class $HasMultipleInjectConstructors.name has multiple constructors with @Inject annotation."
+    }
+
+    public static class HasDefaultConstructor {
+    }
+
+    public static class HasBrokenConstructor {
+        static def failure = new RuntimeException()
+
+        HasBrokenConstructor() {
+            throw failure
+        }
+    }
+
+    public static class HasInjectConstructor {
+        String param1
+        Number param2
+
+        @Inject
+        HasInjectConstructor(String param1, Number param2) {
+            this.param1 = param1
+            this.param2 = param2
+        }
+    }
+
+    public static class AcceptsPrimitiveTypes {
+        int param1
+        boolean param2
+
+        @Inject
+        AcceptsPrimitiveTypes(int param1, boolean param2) {
+            this.param1 = param1
+            this.param2 = param2
+        }
+    }
+
+    public static class HasOneInjectConstructor {
+        HasOneInjectConstructor(String param1) {
+        }
+
+        @Inject
+        HasOneInjectConstructor(Number param1) {
+        }
+    }
+
+    public static class HasNonAnnotatedConstructor {
+        HasNonAnnotatedConstructor(String param1) {
+        }
+    }
+
+    public static class HasNoInjectConstructor {
+        HasNoInjectConstructor(String param1) {
+        }
+
+        HasNoInjectConstructor(Number param1) {
+            throw new AssertionError()
+        }
+
+        HasNoInjectConstructor() {
+            throw new AssertionError()
+        }
+    }
+
+    public static class HasMultipleCompatibleConstructor {
+        HasMultipleCompatibleConstructor(String param1) {
+        }
+
+        HasMultipleCompatibleConstructor(Object param1) {
+        }
+    }
+
+    public static class HasPrivateConstructor {
+        @Inject
+        private HasPrivateConstructor(String param1) {
+        }
+    }
+
+    public static class HasMultipleInjectConstructors {
+        @Inject HasMultipleInjectConstructors(String param1) {
+        }
+
+        @Inject HasMultipleInjectConstructors(Number param1) {
+            throw new AssertionError()
+        }
+
+        @Inject HasMultipleInjectConstructors() {
+            throw new AssertionError()
+        }
+    }
+
+}
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
index e2d40a1..2a3c416 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
@@ -19,11 +19,14 @@ package org.gradle.api.internal
 import spock.lang.Specification
 import org.junit.Rule
 import org.gradle.util.TemporaryFolder
+import org.gradle.util.GradleVersion
 
 class DocumentationRegistryTest extends Specification {
     @Rule TemporaryFolder tmpDir
     final GradleDistributionLocator locator = Mock()
-    final DocumentationRegistry registry = new DocumentationRegistry(locator)
+    final GradleVersion gradleVersion = GradleVersion.current()
+    final DocumentationRegistry registry = new DocumentationRegistry(locator, gradleVersion)
+
 
     def "points users at the local user guide when target page is present in distribution"() {
         def distDir = tmpDir.createDir("home")
@@ -60,7 +63,7 @@ class DocumentationRegistryTest extends Specification {
         _ * locator.gradleHome >> distDir
 
         expect:
-        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/current/userguide/gradle_daemon.html"
+        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/${gradleVersion.version}/userguide/gradle_daemon.html"
     }
 
     def "points users at the remote user guide when no distribution"() {
@@ -68,6 +71,6 @@ class DocumentationRegistryTest extends Specification {
         _ * locator.gradleHome >> null
 
         expect:
-        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/current/userguide/gradle_daemon.html"
+        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/${gradleVersion.version}/userguide/gradle_daemon.html"
     }
 }
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
index 0fcc898..af3f768 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTest.java
@@ -69,7 +69,7 @@ public class ExtensibleDynamicObjectTest {
             assertThat(e.getMessage(), equalTo("Cannot set the value of read-only property 'readOnlyProperty' on <bean>."));
         }
     }
-    
+
     @Test
     public void canSetWriteOnlyClassProperty() {
         Bean bean = new Bean();
@@ -100,6 +100,7 @@ public class ExtensibleDynamicObjectTest {
     @Test
     public void groovyObjectHasPropertiesDefinedByClassMetaInfo() {
         GroovyBean bean = new GroovyBean();
+        ExtensibleDynamicObjectTestHelper.decorateGroovyBean(bean);
         assertTrue(bean.hasProperty("groovyProperty"));
         assertTrue(bean.hasProperty("dynamicGroovyProperty"));
     }
@@ -117,30 +118,31 @@ public class ExtensibleDynamicObjectTest {
         GroovyBean bean = new GroovyBean();
         bean.setGroovyProperty("value");
 
-        assertThat(((Bean)bean).getProperty("groovyProperty"), equalTo((Object) "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) bean).getProperty("groovyProperty"), equalTo((Object) "new value"));
         assertThat(bean.getGroovyProperty(), equalTo((Object) "new value"));
     }
 
     @Test
     public void canGetAndSetGroovyDynamicProperty() {
         GroovyBean bean = new GroovyBean();
+        ExtensibleDynamicObjectTestHelper.decorateGroovyBean(bean);
 
-        assertThat(((Bean)bean).getProperty("dynamicGroovyProperty"), equalTo(null));
+        assertThat(((Bean) bean).getProperty("dynamicGroovyProperty"), equalTo(null));
 
         bean.setProperty("dynamicGroovyProperty", "new value");
 
-        assertThat(((Bean)bean).getProperty("dynamicGroovyProperty"), equalTo((Object) "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());
@@ -148,12 +150,12 @@ public class ExtensibleDynamicObjectTest {
 
     @Test
     public void canGetAndSetPropertiesOnGroovyObjectFromJava() {
-        assertCanGetAndSetProperties(new GroovyBean());
+        assertCanGetAndSetProperties(new GroovyBean().getAsDynamicObject());
     }
 
     @Test
     public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromJava() {
-        assertCanGetAndSetProperties(new DynamicBean());
+        assertCanGetAndSetProperties(new DynamicJavaBean().getAsDynamicObject());
     }
 
     private void assertCanGetAndSetProperties(DynamicObject bean) {
@@ -167,7 +169,7 @@ public class ExtensibleDynamicObjectTest {
 
     @Test
     public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromGroovy() {
-        ExtensibleDynamicObjectTestHelper.assertCanGetAndSetProperties(new DynamicBean());
+        ExtensibleDynamicObjectTestHelper.assertCanGetAndSetProperties(new DynamicJavaBean());
     }
 
     @Test
@@ -207,7 +209,7 @@ public class ExtensibleDynamicObjectTest {
         Bean bean = new Bean();
         assertFalse(bean.hasProperty("parentProperty"));
 
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
         assertTrue(bean.hasProperty("parentProperty"));
     }
 
@@ -217,7 +219,7 @@ public class ExtensibleDynamicObjectTest {
         parent.setProperty("parentProperty", "value");
 
         Bean bean = new Bean();
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
 
         assertThat(bean.getProperty("parentProperty"), equalTo((Object) "value"));
     }
@@ -227,7 +229,7 @@ public class ExtensibleDynamicObjectTest {
         Bean parent = new Bean();
 
         Bean bean = new Bean();
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
         bean.setProperty("parentProperty", "value");
 
         assertFalse(parent.hasProperty("parentProperty"));
@@ -260,7 +262,7 @@ public class ExtensibleDynamicObjectTest {
         otherObject.setProperty("otherObject", "value");
 
         Bean bean = new Bean();
-        bean.extensibleDynamicObject.addObject(otherObject, ExtensibleDynamicObject.Location.BeforeConvention);
+        bean.extensibleDynamicObject.addObject(otherObject.getAsDynamicObject(), ExtensibleDynamicObject.Location.BeforeConvention);
 
         assertTrue(bean.hasProperty("otherObject"));
         assertThat(bean.getProperty("otherObject"), equalTo((Object) "value"));
@@ -268,7 +270,7 @@ public class ExtensibleDynamicObjectTest {
 
         assertThat(otherObject.getProperty("otherObject"), equalTo((Object) "new value"));
     }
-    
+
     @Test
     public void classPropertyTakesPrecedenceOverAdditionalProperty() {
         Bean bean = new Bean();
@@ -308,7 +310,7 @@ public class ExtensibleDynamicObjectTest {
         parent.setProperty("conventionProperty", "parent");
 
         Bean bean = new Bean();
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
 
         Convention convention = bean.extensibleDynamicObject.getConvention();
         ConventionBean conventionBean = new ConventionBean();
@@ -336,7 +338,7 @@ public class ExtensibleDynamicObjectTest {
         ConventionBean conventionBean = new ConventionBean();
         conventionBean.setConventionProperty("conventionProperty");
         convention.getPlugins().put("bean", conventionBean);
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
 
         Map<String, Object> properties = bean.getProperties();
         assertThat(properties.get("properties"), sameInstance((Object) properties));
@@ -353,7 +355,7 @@ public class ExtensibleDynamicObjectTest {
     public void canGetAllPropertiesFromGroovy() {
         ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new Bean());
         ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new GroovyBean());
-        ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new DynamicBean());
+        ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new DynamicJavaBean());
     }
 
     @Test
@@ -367,12 +369,12 @@ public class ExtensibleDynamicObjectTest {
             assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
         }
 
-        bean.setParent(new Bean(){
+        bean.setParent(new Bean() {
             @Override
             public String toString() {
                 return "<parent>";
             }
-        });
+        }.getAsDynamicObject());
 
         try {
             bean.getProperty("unknown");
@@ -393,18 +395,19 @@ public class ExtensibleDynamicObjectTest {
     public void canInvokeMethodDefinedByClass() {
         Bean bean = new Bean();
         assertTrue(bean.hasMethod("javaMethod", "a", "b"));
-        assertThat(bean.invokeMethod("javaMethod", "a", "b"), equalTo((Object) "java:a.b"));
+        assertThat(bean.getAsDynamicObject().invokeMethod("javaMethod", "a", "b"), equalTo((Object) "java:a.b"));
     }
 
     @Test
     public void canInvokeMethodDefinedByMetaClass() {
         Bean bean = new GroovyBean();
+        ExtensibleDynamicObjectTestHelper.decorateGroovyBean(bean);
 
         assertTrue(bean.hasMethod("groovyMethod", "a", "b"));
-        assertThat(bean.invokeMethod("groovyMethod", "a", "b"), equalTo((Object) "groovy:a.b"));
+        assertThat(bean.getAsDynamicObject().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"));
+        assertThat(bean.getAsDynamicObject().invokeMethod("dynamicGroovyMethod", "a", "b"), equalTo((Object) "dynamicGroovy:a.b"));
     }
 
     @Test
@@ -414,7 +417,7 @@ public class ExtensibleDynamicObjectTest {
         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"));
+        assertThat(bean.getAsDynamicObject().invokeMethod("scriptMethod", "a", "b").toString(), equalTo((Object) "script:a.b"));
     }
 
     @Test
@@ -426,7 +429,7 @@ public class ExtensibleDynamicObjectTest {
 
         convention.getPlugins().put("bean", new ConventionBean());
         assertTrue(bean.hasMethod("conventionMethod", "a", "b"));
-        assertThat(bean.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+        assertThat(bean.getAsDynamicObject().invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
     }
 
     @Test
@@ -439,11 +442,11 @@ public class ExtensibleDynamicObjectTest {
         Bean bean = new Bean();
 
         assertFalse(bean.hasMethod("parentMethod", "a", "b"));
-        
-        bean.setParent(parent);
+
+        bean.setParent(parent.getAsDynamicObject());
 
         assertTrue(bean.hasMethod("parentMethod", "a", "b"));
-        assertThat(bean.invokeMethod("parentMethod", "a", "b"), equalTo((Object) "parent:a.b"));
+        assertThat(bean.getAsDynamicObject().invokeMethod("parentMethod", "a", "b"), equalTo((Object) "parent:a.b"));
     }
 
     @Test
@@ -451,7 +454,7 @@ public class ExtensibleDynamicObjectTest {
         Bean bean = new Bean();
         Convention convention = bean.extensibleDynamicObject.getConvention();
         convention.getPlugins().put("bean", new ConventionBean());
-        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+        new ExtensibleDynamicObjectTestHelper().assertCanCallMethods(bean);
     }
 
     @Test
@@ -459,15 +462,23 @@ public class ExtensibleDynamicObjectTest {
         GroovyBean bean = new GroovyBean();
         Convention convention = bean.extensibleDynamicObject.getConvention();
         convention.getPlugins().put("bean", new ConventionBean());
-        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+        new ExtensibleDynamicObjectTestHelper().assertCanCallMethods(bean);
     }
 
     @Test
     public void canInvokeMethodsOnJavaSubClassOfGroovyObjectFromGroovy() {
-        DynamicBean bean = new DynamicBean();
-        Convention convention = bean.extensibleDynamicObject.getConvention();
+        // This doesn't work.
+        // It used to because at the bottom of the hierarchy chain the object implemented methodMissing().
+        // However, our normal “decorated” classes do not do this so it is not realistic.
+
+        // Groovy does something very strange here.
+        // For some reason (probably because the class is Java), it won't employ any dynamism.
+        // Even implementing invokeMethod at the Java level has no effect.
+
+        DynamicJavaBean javaBean = new DynamicJavaBean();
+        Convention convention = javaBean.extensibleDynamicObject.getConvention();
         convention.getPlugins().put("bean", new ConventionBean());
-        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+        new ExtensibleDynamicObjectTestHelper().assertCanCallMethods(javaBean);
     }
 
     @Test
@@ -476,12 +487,12 @@ public class ExtensibleDynamicObjectTest {
         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);
+            bean.getAsDynamicObject().invokeMethod("unknown", "a", 12);
             fail();
         } catch (MissingMethodException e) {
             assertThat(e.getMessage(), equalTo("Could not find method unknown() for arguments [a, 12] on <bean>."));
@@ -532,7 +543,7 @@ public class ExtensibleDynamicObjectTest {
         };
 
         try {
-            bean.invokeMethod("failure");
+            bean.getAsDynamicObject().invokeMethod("failure");
             fail();
         } catch (Exception e) {
             assertThat(e, sameInstance((Exception) failure));
@@ -567,7 +578,7 @@ public class ExtensibleDynamicObjectTest {
         Bean other = new Bean();
         other.setProperty("other", "value");
         Bean bean = new Bean();
-        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+        bean.extensibleDynamicObject.addObject(other.getAsDynamicObject(), ExtensibleDynamicObject.Location.BeforeConvention);
 
         DynamicObject inherited = bean.getInheritable();
         assertTrue(inherited.hasProperty("other"));
@@ -584,7 +595,7 @@ public class ExtensibleDynamicObjectTest {
         DynamicObject inherited = bean.getInheritable();
         assertFalse(inherited.hasProperty("other"));
 
-        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+        bean.extensibleDynamicObject.addObject(other.getAsDynamicObject(), ExtensibleDynamicObject.Location.BeforeConvention);
 
         assertTrue(inherited.hasProperty("other"));
         assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
@@ -625,7 +636,7 @@ public class ExtensibleDynamicObjectTest {
         Bean parent = new Bean();
         parent.setProperty("parentProperty", "value");
         Bean bean = new Bean();
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
 
         DynamicObject inherited = bean.getInheritable();
         assertTrue(inherited.hasProperty("parentProperty"));
@@ -675,7 +686,7 @@ public class ExtensibleDynamicObjectTest {
         convention.getPlugins().put("convention", new ConventionBean());
 
         Bean bean = new Bean();
-        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+        bean.extensibleDynamicObject.addObject(other.getAsDynamicObject(), ExtensibleDynamicObject.Location.BeforeConvention);
 
         DynamicObject inherited = bean.getInheritable();
         assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
@@ -688,7 +699,7 @@ public class ExtensibleDynamicObjectTest {
         Convention convention = parent.extensibleDynamicObject.getConvention();
         convention.getPlugins().put("convention", new ConventionBean());
         Bean bean = new Bean();
-        bean.setParent(parent);
+        bean.setParent(parent.getAsDynamicObject());
 
         DynamicObject inherited = bean.getInheritable();
         assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
@@ -703,13 +714,13 @@ public class ExtensibleDynamicObjectTest {
         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));
+        assertThat(DynamicObjectUtil.asDynamicObject(bean), sameInstance((DynamicObject) bean.getAsDynamicObject()));
 
-        AbstractProject project = (AbstractProject)ProjectBuilder.builder().build();
+        AbstractProject project = (AbstractProject) ProjectBuilder.builder().build();
         assertThat(DynamicObjectUtil.asDynamicObject(project), sameInstance(project.getAsDynamicObject()));
 
         assertThat(DynamicObjectUtil.asDynamicObject(new Object()), instanceOf(DynamicObject.class));
@@ -719,7 +730,7 @@ public class ExtensibleDynamicObjectTest {
     public void canGetAndSetGroovyDynamicProperties() {
         DynamicObject object = new BeanDynamicObject(new DynamicGroovyBean());
         object.setProperty("foo", "bar");
-        assertThat((String)object.getProperty("foo"), equalTo("bar"));
+        assertThat((String) object.getProperty("foo"), equalTo("bar"));
 
         try {
             object.getProperty("additional");
@@ -739,7 +750,7 @@ public class ExtensibleDynamicObjectTest {
     @Test
     public void canCallGroovyDynamicMethods() {
         DynamicObject object = new BeanDynamicObject(new DynamicGroovyBean());
-        Integer doubled = (Integer)object.invokeMethod("bar", 1);
+        Integer doubled = (Integer) object.invokeMethod("bar", 1);
         assertThat(doubled, equalTo(2));
 
         try {
@@ -750,18 +761,21 @@ public class ExtensibleDynamicObjectTest {
         }
     }
 
-    public static class Bean implements DynamicObject {
+    public static class Bean extends GroovyObjectSupport implements DynamicObjectAware {
         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());
         }
 
+        public DynamicObject getAsDynamicObject() {
+            return extensibleDynamicObject;
+        }
+
         @Override
         public String toString() {
             return "<bean>";
@@ -806,7 +820,7 @@ public class ExtensibleDynamicObjectTest {
         public String javaMethod(String a, String b) {
             return String.format("java:%s.%s", a, b);
         }
-        
+
         public Object getProperty(String name) {
             return extensibleDynamicObject.getProperty(name);
         }
@@ -827,16 +841,8 @@ public class ExtensibleDynamicObjectTest {
             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 Object invokeMethod(String name, Object args) {
+            return extensibleDynamicObject.invokeMethod(name, (args instanceof Object[]) ? (Object[]) args : new Object[]{args});
         }
 
         public DynamicObject getInheritable() {
@@ -844,7 +850,7 @@ public class ExtensibleDynamicObjectTest {
         }
     }
 
-    private static class DynamicBean extends GroovyBean {
+    private static class DynamicJavaBean extends GroovyBean {
     }
 
     private static class ConventionBean {
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
index 8094bfb..e2da665 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTestHelper.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTestHelper.groovy
@@ -59,35 +59,32 @@ public class ExtensibleDynamicObjectTestHelper {
         assertTrue(bean.hasMethod('conventionMethod', 'a', 'b'))
         assertEquals(bean.conventionMethod('a', 'b'), 'convention:a.b')
     }
+
+    public static void decorateGroovyBean(bean) {
+        Map values = [:]
+        bean.metaClass.getDynamicGroovyProperty << {-> values.dynamicGroovyProperty }
+        bean.metaClass.setDynamicGroovyProperty << {value -> values.dynamicGroovyProperty = value}
+        bean.metaClass.dynamicGroovyMethod << {a, b -> "dynamicGroovy:$a.$b".toString() }
+    }
 }
 
 public class DynamicBean extends ExtensibleDynamicObjectTest.Bean {
-    def propertyMissing(String name) {
-        super.getProperty(name)
-    }
+//    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)
-    }
+//    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()
     }
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
index e42e0cb..9b7e30b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.NamedDomainObjectFactory
 import org.gradle.api.Namer
 import spock.lang.Specification
 import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.reflect.ObjectInstantiationException
 
 class FactoryNamedDomainObjectContainerSpec extends Specification {
     final NamedDomainObjectFactory<String> factory = Mock()
@@ -100,7 +101,7 @@ class FactoryNamedDomainObjectContainerSpec extends Specification {
         getInstance()
 
         then:
-        thrown IllegalArgumentException
+        thrown ObjectInstantiationException
     }
 
     static class NoConstructor {}
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
deleted file mode 100644
index 20a6b6c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/FilteredActionSpec.groovy
+++ /dev/null
@@ -1,69 +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.*
-
-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/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
index 5e01d3f..421ff58 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
@@ -99,7 +99,7 @@ class NestedConfigureAutoCreateNamedDomainObjectContainerSpec extends Specificat
 
 
         then:
-        def e = thrown(MissingMethodException)
+        def e = thrown(groovy.lang.MissingMethodException)
         e.method == "somethingThatDoesntExist"
         parent.c1.m1.prop == "c1c1m1"
         
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 ecb82d5..8f913ce 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
@@ -262,6 +262,35 @@ class XmlTransformerTest extends Specification {
         "\t"     | "  " // tabs not supported, two spaces used instead
     }
 
+    def "can use with action api"() {
+        given:
+        def writer = new StringWriter()
+        def input = "<things><thing/></things>"
+        def generator = new Action<Writer>() {
+            void execute(Writer t) {
+                t.write(input)
+            }
+        }
+
+        when:
+        transformer.transform(writer, generator)
+
+        then:
+        looksLike(input, writer.toString())
+
+        when:
+        writer.buffer.setLength(0)
+        transformer.addAction(new Action<XmlProvider>() {
+            void execute(XmlProvider xml) {
+                xml.asNode().thing[0]. at foo = "bar"
+            }
+        })
+        transformer.transform(writer, generator)
+
+        then:
+        looksLike('<things>\n  <thing foo="bar"/>\n</things>', writer.toString())
+    }
+
     private void looksLike(String expected, String actual) {
         assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected)))
     }
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
index cfeb3a7..0ba1105 100644
--- 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
@@ -16,231 +16,257 @@
 
 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.internal.reflect.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()
+import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+class DefaultArtifactRepositoryContainerTest extends Specification {
+
+    BaseRepositoryFactory baseRepositoryFactory
+    DefaultArtifactRepositoryContainer container
+
+    def setup() {
+        baseRepositoryFactory = Mock(BaseRepositoryFactory)
+        container = createResolverContainer()
+    }
+
+    ArtifactRepositoryContainer createResolverContainer(
+            BaseRepositoryFactory baseRepositoryFactory = baseRepositoryFactory,
+            Instantiator instantiator = new DirectInstantiator()
+    ) {
+        new DefaultArtifactRepositoryContainer(baseRepositoryFactory, instantiator)
     }
 
-    @Test public void testAddResolver() {
-        assert resolverContainer.addLast(expectedUserDescription).is(expectedResolver)
-        assert resolverContainer.findByName(expectedName) != null
-        resolverContainer.addLast(expectedUserDescription2)
-        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
+    List setupNotation(int i, baseRepositoryFactory = baseRepositoryFactory) {
+        setupNotation("repoNotation$i", "repo$i", "resolver$i", baseRepositoryFactory)
     }
 
-    @Test public void testCannotAddResolverWithDuplicateName() {
-        [expectedResolver, expectedResolver2]*.name = 'resolver'
-        resolverContainer.addLast(expectedUserDescription)
+    List setupNotation(notation, repoName, resolverName, baseRepositoryFactory = baseRepositoryFactory) {
+        def repo = Mock(ArtifactRepositoryInternal) { getName() >> repoName }
+        def resolver = new FileSystemResolver()
+        def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
 
-        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."))
+        interaction {
+            1 * baseRepositoryFactory.createRepository(notation) >> repo
+            1 * baseRepositoryFactory.toResolver(repo) >> resolver
+            1 * baseRepositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            1 * resolverRepo.onAddToContainer(container)
         }
+
+        [notation, repo, resolver, resolverRepo]
     }
 
-    @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
+    def "can add resolver"() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+
+        expect:
+        container.addLast(repo1Notation).is resolver1
+        assert container.findByName(resolver1.name) != null
+        container.addLast(repo2Notation)
+        container == [resolverRepo1, resolverRepo2]
     }
 
-    @Test public void testAddBefore() {
-        resolverContainer.addLast(expectedUserDescription)
-        assert resolverContainer.addBefore(expectedUserDescription2, expectedName).is(expectedResolver2)
-        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+    def "can add repositories with duplicate names"() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+
+        when:
+        container.addLast(repo1Notation)
+        container.addLast(repo2Notation)
+
+        then:
+        container*.name == ["repository", "repository2"]
     }
 
-    @Test public void testAddAfter() {
-        resolverContainer.addLast(expectedUserDescription)
-        assert resolverContainer.addAfter(expectedUserDescription2, expectedName).is(expectedResolver2)
-        resolverContainer.addAfter(expectedUserDescription3, expectedName)
-        assertEquals([expectedResolver, expectedResolver3, expectedResolver2], resolverContainer.resolvers)
+    def testAddResolverWithClosure() {
+        given:
+        def repo = Mock(ArtifactRepositoryInternal) { getName() >> "name" }
+        def resolver = new FileSystemResolver()
+        def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
+
+        interaction {
+            1 * baseRepositoryFactory.createRepository(resolver) >> repo
+            1 * baseRepositoryFactory.toResolver(repo) >> resolver
+            1 * baseRepositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            1 * resolverRepo.onAddToContainer(container)
+        }
+
+        when:
+        container.add(resolver) {
+            transactional = "foo"
+            name = "bar"
+        }
+
+        then:
+        resolver.transactional == "foo"
+        resolverRepo.name == "bar"
     }
 
-    @Test(expected = UnknownRepositoryException) public void testAddBeforeWithUnknownResolver() {
-        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    def testAddBefore() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+
+        when:
+        container.addLast(repo1Notation)
+        container.addBefore(repo2Notation, "repository")
+
+        then:
+        container == [resolverRepo2, resolverRepo1]
     }
 
-    @Test(expected = UnknownRepositoryException) public void testAddAfterWithUnknownResolver() {
-        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    def testAddAfter() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+        def (repo3Notation, repo3, resolver3, resolverRepo3) = setupNotation(3)
+
+        when:
+        container.addLast(repo1Notation)
+        container.addAfter(repo2Notation, "repository")
+        container.addAfter(repo3Notation, "repository")
+
+        then:
+        container == [resolverRepo1, resolverRepo3, resolverRepo2]
     }
 
-    @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"))
-        }
+    def testAddBeforeWithUnknownResolver() {
+        when:
+        container.addBefore("asdfasd", 'unknownName')
 
-        resolverContainer.addFirst(repository1)
-        resolverContainer.addFirst(repository2)
+        then:
+        thrown(UnknownRepositoryException)
+    }
+
+    def testAddAfterWithUnknownResolver() {
+        when:
+        container.addAfter("asdfasd", 'unknownName')
 
-        assert resolverContainer == [repository2, repository1]
-        assert resolverContainer.collect { it } == [repository2, repository1]
-        assert resolverContainer.matching { true } == [repository2, repository1]
-        assert resolverContainer.matching { true }.collect { it } == [repository2, repository1]
+        then:
+        thrown(UnknownRepositoryException)
     }
 
-    @Test public void testAddLast() {
-        ArtifactRepository repository1 = context.mock(ArtifactRepository)
-        ArtifactRepository repository2 = context.mock(ArtifactRepository)
+    def testAddFirst() {
+        given:
+        def repo1 = Mock(ArtifactRepository) { getName() >> "a" }
+        def repo2 = Mock(ArtifactRepository) { getName() >> "b" }
 
-        context.checking {
-            allowing(repository1).getName(); will(returnValue('repo1'))
-            allowing(repository2).getName(); will(returnValue('repo2'))
-        }
-        
-        resolverContainer.addLast(repository1)
-        resolverContainer.addLast(repository2)
+        when:
+        container.addFirst(repo1)
+        container.addFirst(repo2)
 
-        assert resolverContainer == [repository1, repository2]
+        then:
+        container == [repo2, repo1]
+        container.collect { it } == [repo2, repo1]
+        container.matching { true } == [repo2, repo1]
+        container.matching { true }.collect { it } == [repo2, repo1]
     }
-    
-    @Test public void testAddFirstUsingUserDescription() {
-        assert resolverContainer.addFirst(expectedUserDescription).is(expectedResolver)
-        resolverContainer.addFirst(expectedUserDescription2)
-        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+
+    def testAddLast() {
+        given:
+        def repo1 = Mock(ArtifactRepository) { getName() >> "a" }
+        def repo2 = Mock(ArtifactRepository) { getName() >> "b" }
+
+        when:
+        container.addLast(repo1)
+        container.addLast(repo2)
+
+        then:
+        container == [repo1, repo2]
     }
 
-    @Test public void testAddLastUsingUserDescription() {
-        assert resolverContainer.addLast(expectedUserDescription).is(expectedResolver)
-        resolverContainer.addLast(expectedUserDescription2)
-        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
+    def testAddFirstUsingUserDescription() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+
+        when:
+        container.addFirst(repo1Notation)
+        container.addFirst(repo2Notation)
+
+        then:
+        container == [resolverRepo2, resolverRepo1]
+    }
+
+    def testAddLastUsingUserDescription() {
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        def (repo2Notation, repo2, resolver2, resolverRepo2) = setupNotation(2)
+
+        when:
+        container.addLast(repo1Notation)
+        container.addLast(repo2Notation)
+
+        then:
+        container == [resolverRepo1, resolverRepo2]
     }
 
-    @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."))
-        }
+        given:
+        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
+        resolver1.name = null
+
+        when:
+        container.addLast(repo1Notation)
+
+        then:
+        resolver1.name == 'repository'
     }
 
-    @Test
-    public void notificationsAreFiredWhenRepositoryIsAdded() {
-        Action<DependencyResolver> action = context.mock(Action.class)
-        ArtifactRepository repository = context.mock(ArtifactRepository)
+    def testGetThrowsExceptionForUnknownResolver() {
+        when:
+        container.getByName("unknown")
 
-        context.checking {
-            ignoring(repository)
-            one(action).execute(repository)
-        }
+        then:
+        def e = thrown(UnknownRepositoryException)
+        e.message == "Repository with name 'unknown' not found."
+    }
 
-        resolverContainer.all(action)
-        resolverContainer.add(repository)
+    def notificationsAreFiredWhenRepositoryIsAdded() {
+        Action<ArtifactRepository> action = Mock(Action)
+        ArtifactRepository repository = Mock(ArtifactRepository)
+
+        when:
+        container.all(action)
+        container.add(repository)
+
+        then:
+        1 * action.execute(repository)
     }
 
-    @Test
-    public void notificationsAreFiredWhenRepositoryIsAddedToTheHead() {
-        Action<DependencyResolver> action = context.mock(Action.class)
-        ArtifactRepository repository = context.mock(ArtifactRepository)
+    def notificationsAreFiredWhenRepositoryIsAddedToTheHead() {
+        Action<ArtifactRepository> action = Mock(Action)
+        ArtifactRepository repository = Mock(ArtifactRepository)
 
-        context.checking {
-            ignoring(repository)
-            one(action).execute(repository)
-        }
+        when:
+        container.all(action)
+        container.addFirst(repository)
 
-        resolverContainer.all(action)
-        resolverContainer.addFirst(repository)
+        then:
+        1 * action.execute(repository)
     }
 
-    @Test
-    public void notificationsAreFiredWhenRepositoryIsAddedToTheTail() {
-        Action<DependencyResolver> action = context.mock(Action.class)
-        ArtifactRepository repository = context.mock(ArtifactRepository)
+    def notificationsAreFiredWhenRepositoryIsAddedToTheTail() {
+        Action<ArtifactRepository> action = Mock(Action)
+        ArtifactRepository repository = Mock(ArtifactRepository)
 
-        context.checking {
-            ignoring(repository)
-            one(action).execute(repository)
-        }
+        when:
+        container.all(action)
+        container.addLast(repository)
 
-        resolverContainer.all(action)
-        resolverContainer.addLast(repository)
+        then:
+        1 * action.execute(repository)
     }
-}
 
-interface TestArtifactRepository extends ArtifactRepository, ArtifactRepositoryInternal {
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.groovy
new file mode 100644
index 0000000..6a4b075
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.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.api.internal.artifacts
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 9/10/12
+ */
+class ModuleVersionSelectorStrictSpecTest extends Specification {
+
+    def "knows if matches the id"() {
+        def selector = newSelector("org", "util", "1.0")
+        def matching = newId("org", "util", "1.0")
+
+        def differentGroup = newId("xorg", "util", "1.0")
+        def differentName = newId("org", "xutil", "1.0")
+        def differentVersion = newId("org", "xutil", "x1.0")
+
+        expect:
+        selector.matchesStrictly(matching)
+
+        !selector.matchesStrictly(differentGroup)
+        !selector.matchesStrictly(differentName)
+        !selector.matchesStrictly(differentVersion)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.groovy
new file mode 100644
index 0000000..00529ba
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ResolverResultsSpec.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.internal.artifacts
+
+import org.gradle.api.artifacts.ResolveException
+import org.gradle.api.artifacts.ResolvedConfiguration
+import org.gradle.api.artifacts.result.ResolutionResult
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/16/12
+ */
+class ResolverResultsSpec extends Specification {
+
+    private resolvedConfiguration = Mock(ResolvedConfiguration)
+    private resolutionResult = Mock(ResolutionResult)
+    private fatalFailure = Mock(ResolveException)
+
+    def "does not provide ResolutionResult in case of fatal failure"() {
+        when:
+        def results = new ResolverResults(resolvedConfiguration, fatalFailure)
+
+        then:
+        results.resolvedConfiguration
+
+        when:
+        results.resolutionResult
+        then:
+        def ex = thrown(ResolveException)
+        ex == fatalFailure
+    }
+
+    def "provides resolve results"() {
+        when:
+        def results = new ResolverResults(resolvedConfiguration, resolutionResult)
+
+        then:
+        results.resolvedConfiguration == resolvedConfiguration
+        results.resolutionResult == resolutionResult
+    }
+}
\ No newline at end of file
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
index 9e956b9..ced0cab 100644
--- 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
@@ -17,7 +17,9 @@ package org.gradle.api.internal.artifacts.configurations
 
 import org.gradle.api.Action
 import org.gradle.api.Task
+import org.gradle.api.artifacts.result.ResolutionResult
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
 import org.gradle.api.tasks.TaskDependency
 import org.gradle.listener.ListenerBroadcast
@@ -56,7 +58,7 @@ class DefaultConfigurationSpec extends Specification {
     // 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
+        1 * dependencyResolver.resolve(config) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
         resolvedConfiguration
     }
 
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 c132e11..21d5307 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
@@ -21,11 +21,9 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.*;
+import org.gradle.api.artifacts.result.ResolutionResult;
 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.DefaultPublishArtifactSet;
+import org.gradle.api.internal.artifacts.*;
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
@@ -336,7 +334,7 @@ public class DefaultConfigurationTest {
     private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
         context.checking(new Expectations() {{
             allowing(dependencyResolver).resolve(configuration);
-            will(returnValue(resolvedConfiguration));
+            will(returnValue(new ResolverResults(resolvedConfiguration, context.mock(ResolutionResult.class))));
             allowing(resolvedConfiguration).hasError();
             will(returnValue(withErrors));
         }});
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
index 05d54fd..92dc7ee 100644
--- 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
@@ -16,16 +16,18 @@
 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.DefaultArtifactIdentifier
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import spock.lang.Specification
-import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier
+
+import java.util.concurrent.TimeUnit
 
 public class DefaultCachePolicySpec extends Specification {
     private static final int SECOND = 1000;
@@ -128,7 +130,7 @@ public class DefaultCachePolicySpec extends Specification {
                 t.refresh()
             }
         })
-        cachePolicy.mustRefreshModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), 0)
+        cachePolicy.mustRefreshModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), null, 0)
     }
     
     def "provides details of cached changing module"() {
@@ -203,8 +205,8 @@ public class DefaultCachePolicySpec extends Specification {
 
     private def hasModuleTimeout(int timeout) {
         def module = moduleVersion('group', 'name', 'version')
-        assert !cachePolicy.mustRefreshModule(null, module, timeout);
-        assert !cachePolicy.mustRefreshModule(null, module, timeout - 1)
+        assert !cachePolicy.mustRefreshModule(null, module, null, timeout);
+        assert !cachePolicy.mustRefreshModule(null, module, null, timeout - 1)
         if (timeout == FOREVER) {
             return true
         }
@@ -212,9 +214,9 @@ public class DefaultCachePolicySpec extends Specification {
     }
 
     private def hasMissingModuleTimeout(int timeout) {
-        assert !cachePolicy.mustRefreshModule(null, null, timeout);
-        assert !cachePolicy.mustRefreshModule(null, null, timeout - 1)
-        cachePolicy.mustRefreshModule(null, null, timeout + 1)
+        assert !cachePolicy.mustRefreshModule(null, null, null, timeout);
+        assert !cachePolicy.mustRefreshModule(null, null, null, timeout - 1)
+        cachePolicy.mustRefreshModule(null, null, null, timeout + 1)
     }
 
     private def hasMissingArtifactTimeout(int timeout) {
@@ -230,7 +232,7 @@ public class DefaultCachePolicySpec extends Specification {
     }
     
     private def moduleSelector(String group, String name, String version) {
-        new DefaultModuleVersionIdentifier(group, name, version)
+        new DefaultModuleVersionSelector(group, name, version)
     }
 
     private def moduleIdentifier(String group, String name, String version) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy
new file mode 100644
index 0000000..c174f25
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryFactoryTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Action
+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.ClosureBackedAction
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory
+import spock.lang.Specification
+
+import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME
+import static org.gradle.api.artifacts.ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
+
+class DefaultRepositoryFactoryTest extends Specification {
+
+    BaseRepositoryFactory baseRepositoryFactory = Mock(BaseRepositoryFactory)
+    DefaultRepositoryFactory factory = new DefaultRepositoryFactory(baseRepositoryFactory)
+
+    Action action(Closure closure) {
+        new ClosureBackedAction(closure)
+    }
+
+    def "flat dir repos are given a default name"() {
+        when:
+        def repo = Mock(FlatDirectoryArtifactRepository)
+        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
+        1 * repo.setName(DefaultRepositoryFactory.FLAT_DIR_DEFAULT_NAME)
+
+        then:
+        factory.flatDir(action { }).is(repo)
+    }
+
+    def "can configure flat dir by action"() {
+        when:
+        def repo = Mock(FlatDirectoryArtifactRepository)
+        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
+        1 * repo.setDirs(['a', 'b'])
+        1 * repo.setName('libs')
+        _ * repo.getName() >> "libs"
+
+        then:
+        factory.flatDir(action { name = 'libs'; dirs = ['a', 'b'] }).is(repo)
+    }
+
+    def "can configure flat dir by map"() {
+        when:
+        def repo = Mock(FlatDirectoryArtifactRepository)
+        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
+        1 * repo.setDirs(['a', 'b'])
+        1 * repo.setName('libs')
+        _ * repo.getName() >> "libs"
+
+        then:
+        factory.flatDir([name: 'libs'] + [dirs: ['a', 'b']]).is(repo)
+    }
+
+    def "can configure flat dir by map, one dir"() {
+        when:
+        def repo = Mock(FlatDirectoryArtifactRepository)
+        1 * baseRepositoryFactory.createFlatDirRepository() >> repo
+        1 * repo.setDirs(['a'])
+        1 * repo.setName('libs')
+        _ * repo.getName() >> "libs"
+
+        then:
+        factory.flatDir([name: 'libs'] + [dirs: 'a']).is(repo)
+    }
+
+    public void testMavenCentralWithNoArgs() {
+        when:
+        MavenArtifactRepository repository = Mock(MavenArtifactRepository)
+        1 * baseRepositoryFactory.createMavenCentralRepository() >> repository
+        1 * repository.setName(DEFAULT_MAVEN_CENTRAL_REPO_NAME)
+
+        then:
+        factory.mavenCentral().is(repository)
+    }
+
+    def testMavenLocalWithNoArgs() {
+        when:
+        MavenArtifactRepository repository = Mock(MavenArtifactRepository)
+        1 * baseRepositoryFactory.createMavenLocalRepository() >> repository
+        1 * repository.setName(DEFAULT_MAVEN_LOCAL_REPO_NAME)
+
+        then:
+        factory.mavenLocal().is(repository)
+    }
+
+    def "ivy repos are assigned a default name"() {
+        when:
+        def repo = Mock(IvyArtifactRepository)
+        baseRepositoryFactory.createIvyRepository() >> repo
+        1 * repo.setName("ivy")
+
+        then:
+        factory.ivy(action { })
+    }
+
+    def testIvyWithAction() {
+        when:
+        def repo = Mock(IvyArtifactRepository)
+        baseRepositoryFactory.createIvyRepository() >> repo
+
+        1 * repo.setName("foo")
+
+        then:
+        factory.ivy(action { name = "foo" })
+    }
+
+    def testMavenWithAction() {
+        when:
+        def repo = Mock(MavenArtifactRepository)
+        baseRepositoryFactory.createMavenRepository() >> repo
+
+        1 * repo.setName("foo")
+
+        then:
+        factory.maven(action { name = "foo" })
+    }
+
+}
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 cb5772d..aab6056 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
@@ -17,356 +17,213 @@
 package org.gradle.api.internal.artifacts.dsl
 
 import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.apache.ivy.plugins.resolver.FileSystemResolver
 import org.gradle.api.Action
 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.internal.reflect.DirectInstantiator
+import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainerTest
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
-import org.jmock.integration.junit4.JMock
+import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository
+import org.gradle.internal.reflect.Instantiator
 import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.assertEquals
 
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock)
 class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTest {
-    private DefaultRepositoryHandler repositoryHandler
-
-    public ArtifactRepositoryContainer createResolverContainer() {
-        repositoryHandler = new DefaultRepositoryHandler(resolverFactoryMock, new DirectInstantiator());
-        return repositoryHandler;
-    }
 
-    @Test public void testFlatDirWithClosure() {
-        def repository = context.mock(FlatDirectoryArtifactRepository)
+    RepositoryFactoryInternal repositoryFactory
+    DefaultRepositoryHandler handler
 
-        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() {
-        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)
+    def setup() {
+        repositoryFactory = Mock(RepositoryFactoryInternal)
+        repositoryFactory.getBaseRepositoryFactory() >> baseRepositoryFactory
+        handler = createRepositoryHandler()
     }
 
-    @Test public void testFlatDirWithNameAndSingleDir() {
-        def repository = context.mock(FlatDirectoryArtifactRepository)
+    public ArtifactRepositoryContainer createRepositoryHandler(
+            RepositoryFactoryInternal repositoryFactory = repositoryFactory,
+            Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+    ) {
+        new DefaultRepositoryHandler(repositoryFactory, instantiator)
+    }
 
-        context.checking {
-            one(resolverFactoryMock).createFlatDirRepository(); will(returnValue(repository))
-            one(repository).setDirs(['a'])
-            one(repository).setName('libs')
-            allowing(repository).getName(); will(returnValue('libs'))
-        }
+    def testFlatDirWithClosure() {
+        given:
+        def repository = Mock(TestFlatDirectoryArtifactRepository)
+        1 * repositoryFactory.flatDir(_ as Action) >> repository
 
-        assert repositoryHandler.flatDir([name: 'libs'] + [dirs: 'a']).is(repository)
+        expect:
+        handler.flatDir { name = 'libs' }.is(repository)
     }
 
-    @Test public void testFlatDirWithoutNameAndWithDirs() {
-        def repository = context.mock(FlatDirectoryArtifactRepository)
+    def testFlatDirWithMap() {
+        given:
+        def repository = Mock(TestFlatDirectoryArtifactRepository)
+        1 * repositoryFactory.flatDir(_ as Map) >> repository
 
-        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'))
-        }
-
-        assert repositoryHandler.flatDir([dirs: ['a', 12]]).is(repository)
+        expect:
+        handler.flatDir([name: 'libs'] + [dirs: ['a', 'b']]).is(repository)
     }
 
-    @Test
     public void testMavenCentralWithNoArgs() {
-        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)
-    }
+        when:
+        MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        1 * repositoryFactory.mavenCentral() >> repository
+        repository.getName() >> "name"
 
-    @Test
-    public void testMavenCentralWithSingleUrl() {
-        String testUrl2 = 'http://www.gradle2.org'
-
-        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)
+        then:
+        handler.mavenCentral().is(repository)
     }
 
-    @Test
-    public void testMavenCentralWithNameAndUrls() {
-        String testUrl1 = 'http://www.gradle1.org'
-        String testUrl2 = 'http://www.gradle2.org'
-        String name = 'customName'
-
-        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])
-        }
+    public void testMavenCentralWithMap() {
+        when:
+        MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        1 * repositoryFactory.mavenCentral() >> repository
+        1 * repository.setArtifactUrls(["abc"])
+        repository.getName() >> "name"
 
-        assert repositoryHandler.mavenCentral(name: name, artifactUrls: [testUrl1, testUrl2]).is(repository)
+        then:
+        handler.mavenCentral(artifactUrls: ["abc"]).is(repository)
     }
 
-    @Test
-    public void testMavenLocalWithNoArgs() {
-        MavenArtifactRepository repository = context.mock(MavenArtifactRepository)
-
-        context.checking {
-            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))
-        }
-
-        assert repositoryHandler.mavenLocal() == repository
+    def testMavenLocalWithNoArgs() {
+        when:
+        MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        1 * repositoryFactory.mavenLocal() >> repository
+        repository.getName() >> "name"
+
+        then:
+        handler.mavenLocal().is(repository)
     }
 
-    @Test
-    public void testMavenRepoWithNameAndUrls() {
+    def testMavenRepoWithNameAndUrls() {
+        when:
         String testUrl1 = 'http://www.gradle1.org'
         String testUrl2 = 'http://www.gradle2.org'
         String repoRoot = 'http://www.reporoot.org'
         String repoName = 'mavenRepoName'
 
-        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)
+        TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        repositoryFactory.maven(_ as Action) >> repository
+        1 * repository.setName(repoName)
+        repository.getName() >> repoName
+        1 * repository.setUrl(repoRoot)
+        1 * repository.setArtifactUrls([testUrl1, testUrl2])
+        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        1 * baseRepositoryFactory.toResolver(repository) >> resolver
+
+        then:
+        handler.mavenRepo([name: repoName, url: repoRoot, artifactUrls: [testUrl1, testUrl2]]).is(resolver)
+        handler.size() == 1
+        handler.first() instanceof FixedResolverArtifactRepository
+        handler.first().createResolver() == resolver
     }
 
     @Test
     public void testMavenRepoWithNameAndRootUrlOnly() {
+        when:
         String repoRoot = 'http://www.reporoot.org'
         String repoName = 'mavenRepoName'
 
-        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))
-        }
+        TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        repositoryFactory.maven(_ as Action) >> repository
+        1 * repository.setName(repoName)
+        repository.getName() >> repoName
+        1 * repository.setUrl(repoRoot)
+        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        1 * baseRepositoryFactory.toResolver(repository) >> resolver
 
-        assert repositoryHandler.mavenRepo([name: repoName, url: repoRoot]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
+        then:
+        handler.mavenRepo([name: repoName, url: repoRoot]).is(resolver)
+        handler.size() == 1
+        handler.first().createResolver() == resolver
     }
 
     @Test
     public void testMavenRepoWithoutName() {
-        String testUrl2 = 'http://www.gradle2.org'
+        when:
         String repoRoot = 'http://www.reporoot.org'
 
-        TestMavenArtifactRepository repository = context.mock(TestMavenArtifactRepository)
-
-        context.checking {
-            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))
-        }
+        TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        repositoryFactory.maven(_ as Action) >> repository
+        repository.getName() >> null
+        1 * repository.setUrl(repoRoot)
+        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        1 * baseRepositoryFactory.toResolver(repository) >> resolver
 
-        assert repositoryHandler.mavenRepo([url: repoRoot, artifactUrls: [testUrl2]]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
+        then:
+        handler.mavenRepo([url: repoRoot]).is(resolver)
+        handler.size() == 1
+        handler.first().createResolver() == resolver
     }
 
-    @Test
     public void createIvyRepositoryUsingClosure() {
-        IvyArtifactRepository repository = context.mock(IvyArtifactRepository.class)
-
-        context.checking {
-            one(resolverFactoryMock).createIvyRepository()
-            will(returnValue(repository))
-            allowing(repository).getName()
-            will(returnValue("name"))
-        }
-
-        def arg
-        def result = repositoryHandler.ivy {
-            arg = it
-        }
-
-        assert arg == repository
-        assert result == repository
+        when:
+        def repository = Mock(TestIvyArtifactRepository)
+        1 * repositoryFactory.ivy(_ as Action) >> repository
+
+        then:
+        handler.ivy { }.is repository
     }
 
-    @Test
-    public void createIvyRepositoryUsingAction() {
-        IvyArtifactRepository repository = context.mock(IvyArtifactRepository.class)
-        Action<IvyArtifactRepository> action = context.mock(Action.class)
-
-        context.checking {
-            one(resolverFactoryMock).createIvyRepository()
-            will(returnValue(repository))
-            one(action).execute(repository)
-            allowing(repository).getName()
-            will(returnValue("name"))
-        }
-
-        def result = repositoryHandler.ivy(action)
-        assert result == repository
+    def createIvyRepositoryUsingAction() {
+        when:
+        def repository = Mock(TestIvyArtifactRepository)
+        def action = Mock(Action)
+        1 * repositoryFactory.ivy(action) >> repository
+
+        then:
+        handler.ivy(action).is repository
     }
 
     @Test
     public void providesADefaultNameForIvyRepository() {
-        IvyArtifactRepository repository1 = context.mock(IvyArtifactRepository.class)
-
-        context.checking {
-            one(resolverFactoryMock).createIvyRepository()
-            will(returnValue(repository1))
-            one(repository1).getName()
-            will(returnValue(null))
-            one(repository1).setName("ivy")
-            allowing(repository1).getName()
-            will(returnValue("ivy"))
-        }
-
-        repositoryHandler.ivy { }
-
-        IvyArtifactRepository repository2 = context.mock(IvyArtifactRepository.class)
-
-        context.checking {
-            one(resolverFactoryMock).createIvyRepository()
-            will(returnValue(repository2))
-            allowing(repository2).getName()
-            will(returnValue("ivy2"))
-        }
-
-        repositoryHandler.ivy { }
-
-        IvyArtifactRepository repository3 = context.mock(IvyArtifactRepository.class)
-
-        context.checking {
-            one(resolverFactoryMock).createIvyRepository()
-            will(returnValue(repository3))
-            one(repository3).getName()
-            will(returnValue(null))
-            one(repository3).setName("ivy3")
-            allowing(repository3).getName()
-            will(returnValue("ivy3"))
-        }
-
-        repositoryHandler.ivy { }
+        given:
+        def repo1 = Mock(TestIvyArtifactRepository)
+        def repo1Name = "ivy"
+        repo1.getName() >> { repo1Name }
+        repo1.setName(_) >> { repo1Name = it[0] }
+
+        def repo2 = Mock(TestIvyArtifactRepository)
+        def repo2Name = "ivy"
+        repo2.getName() >> { repo2Name }
+        repo2.setName(_) >> { repo2Name = it[0] }
+
+        def repo3 = Mock(TestIvyArtifactRepository)
+        def repo3Name = "ivy"
+        repo3.getName() >> { repo3Name }
+        repo3.setName(_) >> { repo3Name = it[0] }
+
+        repositoryFactory.ivy(_) >>> [repo1, repo2, repo3]
+
+        when:
+        handler.ivy { }
+        handler.ivy { }
+        handler.ivy { }
+
+        then:
+        repo1Name == "ivy"
+        repo2Name == "ivy2"
+        repo3Name == "ivy3"
     }
 
-    @Test
     public void createMavenRepositoryUsingClosure() {
-        MavenArtifactRepository repository = context.mock(TestMavenArtifactRepository.class)
-
-        context.checking {
-            one(resolverFactoryMock).createMavenRepository()
-            will(returnValue(repository))
-            allowing(repository).getName()
-            will(returnValue("name"))
-        }
-
-        def arg
-        def result = repositoryHandler.maven {
-            arg = it
-        }
-
-        assert arg == repository
-        assert result == repository
+        when:
+        MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        1 * repositoryFactory.maven(_ as Action) >> repository
+
+        then:
+        handler.maven { }.is repository
     }
 
-    @Test
     public void createMavenRepositoryUsingAction() {
-        MavenArtifactRepository repository = context.mock(TestMavenArtifactRepository.class)
-        Action<MavenArtifactRepository> action = context.mock(Action.class)
-
-        context.checking {
-            one(resolverFactoryMock).createMavenRepository()
-            will(returnValue(repository))
-            one(action).execute(repository)
-            allowing(repository).getName()
-            will(returnValue("name"))
-        }
-
-        def result = repositoryHandler.maven(action)
-        assert result == repository
-    }
+        when:
+        MavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
+        def action = Mock(Action)
+        1 * repositoryFactory.maven(action) >> repository
 
-    private DependencyResolver resolver(String name = 'name') {
-        DependencyResolver resolver = context.mock(DependencyResolver.class)
-        context.checking {
-            allowing(resolver).getName(); will(returnValue(name))
-        }
-        return resolver
+        then:
+        handler.maven(action).is repository
     }
 
-    private void prepareName(mavenResolver, String expectedName) {
-        context.checking {
-            one(mavenResolver).setName(expectedName)
-        }
-    }
 }
 
-interface TestMavenArtifactRepository extends MavenArtifactRepository, ArtifactRepositoryInternal {
-}
 
-interface TestFlatDirectoryArtifactRepository extends FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestFlatDirectoryArtifactRepository.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestFlatDirectoryArtifactRepository.java
new file mode 100644
index 0000000..8098c69
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestFlatDirectoryArtifactRepository.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.artifacts.dsl;
+
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+public interface TestFlatDirectoryArtifactRepository extends FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestIvyArtifactRepository.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestIvyArtifactRepository.java
new file mode 100644
index 0000000..4c4212a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestIvyArtifactRepository.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.artifacts.dsl;
+
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+public interface TestIvyArtifactRepository extends ArtifactRepositoryInternal, IvyArtifactRepository {
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestMavenArtifactRepository.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestMavenArtifactRepository.java
new file mode 100644
index 0000000..7e0d6f3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/TestMavenArtifactRepository.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.artifacts.dsl;
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+
+public interface TestMavenArtifactRepository extends MavenArtifactRepository, ArtifactRepositoryInternal {
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryChangingNameAfterContainerInclusionDeprecationTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryChangingNameAfterContainerInclusionDeprecationTest.groovy
new file mode 100644
index 0000000..428c62e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryChangingNameAfterContainerInclusionDeprecationTest.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.api.internal.artifacts.repositories
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.repositories.ArtifactRepository
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.gradle.util.DeprecationLogger
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ArtifactRepositoryChangingNameAfterContainerInclusionDeprecationTest extends Specification {
+
+    TestAppender appender = new TestAppender()
+    ConfigureLogging logging
+    Project project
+
+    def setup() {
+        project = HelperUtil.createRootProject()
+        DeprecationLogger.reset()
+        appender = new TestAppender()
+        logging = new ConfigureLogging(appender)
+        logging.attachAppender()
+    }
+
+    def cleanup() {
+        logging.detachAppender()
+        DeprecationLogger.reset()
+    }
+
+    /**
+     * This is a bit of a weird test. We are assuming that repository impls are extending AbstractArtifactRepository.
+     * Also, we are relying on DefaultReportContainerTest testing that we inform repositories when they are
+     */
+    @Unroll
+    def "logs deprecation warning on name change of #name repo"() {
+        given:
+        ArtifactRepository artifactRepository = repoNotation.call(project.repositories)
+
+        when:
+        artifactRepository.name = "changed"
+
+        then:
+        appender.toString().contains("Changing the name of an ArtifactRepository that is part of a container has been deprecated")
+
+        where:
+        name           | repoNotation
+        "flatDir"      | { it.flatDir {} }
+        "ivy"          | { it.ivy {} }
+        "maven"        | { it.maven {} }
+        "mavenCentral" | { it.mavenCentral() }
+        "mavenLocal"   | { it.mavenLocal() }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparatorSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparatorSpec.groovy
new file mode 100644
index 0000000..b363c9d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/version/LatestVersionSemanticComparatorSpec.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.api.internal.artifacts.version
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+class LatestVersionSemanticComparatorSpec extends Specification {
+
+    private comparator = new LatestVersionSemanticComparator()
+
+    def "compares versions"() {
+        expect:
+        comparator.compare(a, b) < 0
+        comparator.compare(b, a) > 0
+        comparator.compare(a, a) == 0
+        comparator.compare(b, b) == 0
+
+        where:
+        a                   | b
+        '1.0'               | '2.0'
+        '1.2'               | '1.10'
+        '1.0'               | '1.0.1'
+        '1.0-rc-1'          | '1.0-rc-2'
+        '1.0-alpha'         | '1.0'
+        '1.0-alpha'         | '1.0-beta'
+        '1.0-1'             | '1.0-2'
+        '1.0.a'             | '1.0.b'
+        '1.0.alpha'         | '1.0.b'
+    }
+
+    def "equal"() {
+        expect:
+        comparator.compare(a, b) == 0
+        comparator.compare(b, a) == 0
+
+        //some of the comparison are not working hence commented out.
+        //consider updating the implementation when we port the ivy comparison mechanism.
+        where:
+        a                   | b
+        '1.0'               | '1.0'
+        '5.0'               | '5.0'
+//        '1.0.0'             | '1.0'
+//        '1.0.0'             | '1'
+//        '1.0-alpha'         | '1.0-ALPHA'
+//        '1.0.alpha'         | '1.0-alpha'
+    }
+
+    def "not equal"() {
+        expect:
+        comparator.compare(a, b) != 0
+        comparator.compare(b, a) != 0
+
+        where:
+        a                   | b
+        '1.0'               | ''
+        '1.0'               | null
+        '1.0'               | 'hey joe'
+    }
+}
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 478de82..2533aa6 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,26 +16,31 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.util.GFileUtils;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
-import org.gradle.util.TestPrecondition;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.InputStream;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.junit.Assume.assumeTrue;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
 
+ at RunWith(JMock.class)
 public class AbstractFileTreeElementTest {
     @Rule
     public final TemporaryFolder tmpDir = new TemporaryFolder();
+    final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    final Chmod chmod = context.mock(Chmod.class);
 
     @Test
     public void canCopyToOutputStream() {
@@ -52,6 +57,10 @@ public class AbstractFileTreeElementTest {
         TestFile src = writeToFile("src", "content");
         TestFile dest = tmpDir.file("dir/dest");
 
+        context.checking(new Expectations(){{
+            ignoring(chmod);
+        }});
+
         new TestFileTreeElement(src).copyTo(dest);
 
         dest.assertIsFile();
@@ -60,16 +69,14 @@ public class AbstractFileTreeElementTest {
 
     @Test
     public void copiedFileHasExpectedPermissions() throws Exception {
-        assumeTrue(TestPrecondition.FILE_PERMISSIONS.isFulfilled());
-
         TestFile src = writeToFile("src", "");
-        TestFile dest = tmpDir.file("dest");
+        final TestFile dest = tmpDir.file("dest");
 
-        new TestFileTreeElement(src, 0666).copyTo(dest);
-        assertPermissionsEquals("666", dest);
+        context.checking(new Expectations(){{
+            one(chmod).chmod(dest, 0666);
+        }});
 
-        new TestFileTreeElement(src, 0644).copyTo(dest);
-        assertPermissionsEquals("644", dest);
+        new TestFileTreeElement(src, 0666).copyTo(dest);
     }
 
     @Test
@@ -82,16 +89,11 @@ public class AbstractFileTreeElementTest {
     }
 
     private TestFile writeToFile(String name, String content) {
-        final TestFile result = tmpDir.file(name);
+        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;
@@ -125,6 +127,11 @@ public class AbstractFileTreeElementTest {
             return file.length();
         }
 
+        @Override
+        protected Chmod getChmod() {
+            return chmod;
+        }
+
         public RelativePath getRelativePath() {
             throw new UnsupportedOperationException();
         }
@@ -134,9 +141,7 @@ public class AbstractFileTreeElementTest {
         }
 
         public int getMode() {
-            return mode == null
-                ? super.getMode()
-                : mode;
+            return mode == null ? super.getMode() : mode;
         }
     }
 }
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
index 29d9a7d..eabc636 100755
--- 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
@@ -38,7 +38,6 @@ 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()
@@ -337,7 +336,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def resolver() {
-        return new BaseDirFileResolver(FileSystems.default, tmpDir.testDir)
+        return TestFiles.resolver(tmpDir.testDir)
     }
 }
 
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 37c7ccd..2004394 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,24 +15,18 @@
  */
 package org.gradle.api.internal.file.copy
 
-import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.TestFiles
 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
  */
 class DeleteActionImplTest extends Specification {
     @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder();
-
-    FileResolver fileResolver = new BaseDirFileResolver(FileSystems.default, tmpDir.getDir())
-    
-    DeleteActionImpl delete = new DeleteActionImpl(fileResolver);
+    TemporaryFolder tmpDir = new TemporaryFolder()
+    DeleteActionImpl delete = new DeleteActionImpl(TestFiles.resolver(tmpDir.dir))
 
     def deletesDirectory() {
         TestFile dir = tmpDir.getDir();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy
new file mode 100644
index 0000000..c4cc453
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathKeyFileStoreTest.groovy
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Action
+import org.gradle.api.GradleException
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class PathKeyFileStoreTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+    TestFile fsBase
+    PathKeyFileStore store
+
+    def pathCounter = 0
+
+    def setup() {
+        fsBase = dir.file("fs")
+        store = new PathKeyFileStore(fsBase)
+    }
+
+    def "move vs copy"() {
+        given:
+        createFile("a", "a").exists()
+        createFile("b", "b").exists()
+
+        when:
+        store.move("a", dir.file("a"))
+        store.copy("b", dir.file("b"))
+
+        then:
+        !dir.file("a").exists()
+        dir.file("b").exists()
+    }
+
+    def "can move to filestore"() {
+        when:
+        store.move("a", createFile("abc"))
+        store.move("b", createFile("def"))
+
+        then:
+        fsBase.file("a").text == "abc"
+        fsBase.file("b").text == "def"
+    }
+
+    def "can add to filestore"() {
+        when:
+        store.add("a", { File f -> f.text = "abc"} as Action<File>)
+        store.add("b", { File f -> f.text = "def"} as Action<File>)
+        then:
+        fsBase.file("a").text == "abc"
+        fsBase.file("b").text == "def"
+    }
+
+    def "add throws GradleException if exception in action occured"() {
+        when:
+        store.add("a", { File f -> throw new Exception("TestException")} as Action<File>)
+        then:
+        thrown(GradleException)
+        !fsBase.file("a").exists()
+        !fsBase.file("a.fslock").exists()
+    }
+
+    def "can get from filestore"() {
+        when:
+        createFile("abc", "fs/a")
+        then:
+        store.get("a") != null
+        store.get("b") == null
+    }
+
+    def "get cleans up filestore"() {
+        when:
+        createFile("abc", "fs/a").exists()
+        createFile("lock", "fs/a.fslck").exists()
+        then:
+        store.get("a") == null
+        store.get("a.fslock") == null
+    }
+
+    def "can overwrite stale files "() {
+        given:
+        createFile("abc", "fs/a").exists()
+        createFile("lock", "fs/a.fslck").exists()
+        when:
+        store.add("a", { File f -> f.text = "def"} as Action<File>)
+        then:
+        store.get("a").file.text == "def"
+    }
+
+    def "get on stale file with marker removes file from filestore"() {
+        when:
+        createFile("abc", "fs/a")
+        createFile("def", "fs/b")
+        then:
+        store.get("a").file.text == "abc"
+        store.get("b").file.text == "def"
+    }
+
+    def "can overwrite entry"() {
+        when:
+        store.move("a", createFile("abc"))
+        store.move("a", createFile("def"))
+
+        then:
+        fsBase.file("a").text == "def"
+    }
+
+    def "creates intermediary directories"() {
+        when:
+        store.move("a/b/c", createFile("abc"))
+        store.move("a/b/d", createFile("abd"))
+        store.move("a/c/a", createFile("aca"))
+
+        then:
+        fsBase.file("a/b").directory
+        fsBase.file("a/b/c").text == "abc"
+        fsBase.file("a/c/a").text == "aca"
+    }
+
+    def "can search via globs"() {
+        when:
+        store.move("a/a/a", createFile("a"))
+        store.move("a/a/b", createFile("b"))
+        store.move("a/b/a", createFile("c"))
+
+        then:
+        store.search("**/a").size() == 2
+        store.search("*/b/*").size() == 1
+        store.search("a/b/a").size() == 1
+    }
+
+    def "search ignores stale entries with marker file"() {
+        when:
+        store.move("a/a/a", createFile("a"))
+        store.move("a/b/b", createFile("b"))
+        store.move("a/c/c", createFile("c"))
+        createFile("lock", "fs/a/b/b.fslck")
+        def search = store.search("**/*")
+        then:
+        search.size() == 2
+        search.collect {entry -> entry.file.name}.sort() == ["a", "c"]
+    }
+
+    def "move filestore"() {
+        given:
+        def a = store.move("a", createFile("abc"))
+        def b = store.move("b", createFile("def"))
+
+        expect:
+        a.file == dir.file("fs/a")
+        b.file == dir.file("fs/b")
+
+        when:
+        store.moveFilestore(dir.file("new-store"))
+
+        then:
+        store.baseDir == dir.file("new-store")
+
+        and:
+        a.file == dir.file("new-store/a")
+        b.file == dir.file("new-store/b")
+
+        !dir.file("fs").exists()
+    }
+
+    def "can move filestore that doesn't exist yet"() {
+        expect:
+        !store.baseDir.exists()
+
+        when:
+        store.moveFilestore(dir.file("new-filestore"))
+
+        then:
+        notThrown Exception
+    }
+
+    def createFile(String content, String path = "f${pathCounter++}") {
+        dir.createFile(path) << content
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.groovy
new file mode 100644
index 0000000..cbe5916
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/PathNormalisingKeyFileStoreTest.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.api.internal.filestore
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.Action
+
+class PathNormalisingKeyFileStoreTest extends Specification {
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+    TestFile fsBase
+    PathNormalisingKeyFileStore store
+
+    def pathCounter = 0
+
+    def setup() {
+        fsBase = dir.createDir("fs")
+        store = new PathNormalisingKeyFileStore(new PathKeyFileStore(fsBase))
+    }
+
+    def "can move to filestore"() {
+        when:
+        store.move("!.zip", file("abc"))
+        store.move("  ", file("def"))
+
+        then:
+        fsBase.file("_.zip").text == "abc"
+        fsBase.file("__").text == "def"
+    }
+
+    def "can add to filestore"() {
+        when:
+        store.add("!.zip", {File file -> file.text = "abc"} as Action<File>)
+        store.add("  ", {File file -> file.text = "def"} as Action<File>)
+        then:
+        fsBase.file("_.zip").text == "abc"
+        fsBase.file("__").text == "def"
+    }
+
+    def "can overwrite entry"() {
+        when:
+        store.move("!", file("abc"))
+        store.move(" ", file("def"))
+
+        then:
+        fsBase.file("_").text == "def"
+    }
+
+    def "creates intermediary directories"() {
+        when:
+        store.move("a/!/c", file("abc"))
+        store.move("a/ /d", file("abd"))
+        store.move("a/c/(", file("aca"))
+
+        then:
+        fsBase.file("a/_").directory
+        fsBase.file("a/_/c").text == "abc"
+        fsBase.file("a/c/_").text == "aca"
+    }
+
+    def "can search via globs"() {
+        when:
+        store.move("a/!/a", file("a"))
+        store.move("a/ /b", file("b"))
+        store.move("a/b/&", file("c"))
+
+        then:
+        store.search("**/a").size() == 1
+        store.search("*/ /*").size() == 2
+        store.search("a/b/_").size() == 1
+    }
+
+    def file(String content, String path = "f${pathCounter++}") {
+        dir.createFile(path) << content
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy
new file mode 100644
index 0000000..92c4bab
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/filestore/UniquePathKeyFileStoreTest.groovy
@@ -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.filestore
+
+import org.gradle.api.Action
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class UniquePathKeyFileStoreTest extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder = new TemporaryFolder();
+    Action<File> action = Mock()
+
+    UniquePathKeyFileStore uniquePathKeyFileStore
+
+    def setup() {
+        uniquePathKeyFileStore = new UniquePathKeyFileStore(temporaryFolder.createDir("fsbase"))
+    }
+
+    def "add executes action if file does not exist"() {
+        def file = temporaryFolder.file("fsbase/a/a");
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.add("a/a", action)
+
+        then:
+        fileInStore.file == file
+        1 * action.execute(file) >> { File f -> f.text = 'hi' }
+    }
+
+    def "add skips action if file already exists"() {
+        setup:
+        def file = temporaryFolder.createFile("fsbase/a/a");
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.add("a/a", action)
+
+        then:
+        fileInStore.file == file
+        0 * action.execute(_)
+    }
+
+    def "copy returns existing file it it already exists"() {
+        setup:
+        def source = temporaryFolder.createFile("some-file")
+        def file = temporaryFolder.createFile("fsbase/a/a");
+        file.text = 'existing content'
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.copy("a/a", source)
+
+        then:
+        fileInStore.file == file
+        file.text == 'existing content'
+    }
+
+    def "copy adds file it it does not exist"() {
+        setup:
+        def source = temporaryFolder.createFile("some-file")
+        def file = temporaryFolder.file("fsbase/a/a");
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.copy("a/a", source)
+
+        then:
+        fileInStore.file == file
+        file.assertIsCopyOf(source)
+    }
+
+    def "move returns existing file it it already exists"() {
+        setup:
+        def source = temporaryFolder.createFile("some-file")
+        def file = temporaryFolder.createFile("fsbase/a/a");
+        file.text = 'existing content'
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.move("a/a", source)
+
+        then:
+        fileInStore.file == file
+        file.text == 'existing content'
+        !source.exists()
+    }
+
+    def "move adds file it it does not exist"() {
+        setup:
+        def source = temporaryFolder.createFile("some-file")
+        def file = temporaryFolder.file("fsbase/a/a");
+
+        when:
+        def fileInStore = uniquePathKeyFileStore.move("a/a", source)
+
+        then:
+        fileInStore.file == file
+        !source.exists()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.groovy
new file mode 100644
index 0000000..9b75e9f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ClosureToSpecNotationParserSpec.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.api.internal.notations.parsers
+
+import org.gradle.api.internal.notations.api.UnsupportedNotationException
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/12/12
+ */
+class ClosureToSpecNotationParserSpec extends Specification {
+
+    private ClosureToSpecNotationParser parser = new ClosureToSpecNotationParser()
+
+    def "converts closures"() {
+        expect:
+        parser.parseNotation({ it == 'foo' }).isSatisfiedBy("foo")
+        !parser.parseNotation({ it == 'foo' }).isSatisfiedBy("bar")
+
+        when:
+        parser.parseNotation("oups")
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.groovy
new file mode 100644
index 0000000..e542623
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.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.internal.plugins
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.TestPlugin1
+import org.gradle.api.internal.project.TestPlugin2
+import org.gradle.api.plugins.PluginInstantiationException
+import org.gradle.api.plugins.UnknownPluginException
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.reflect.ObjectInstantiationException
+import org.gradle.util.GUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultPluginRegistryTest extends Specification {
+    @Rule
+    final TemporaryFolder testDir = new TemporaryFolder()
+    final Instantiator instantiator = Mock()
+    final ClassLoader classLoader = Mock()
+    private DefaultPluginRegistry pluginRegistry = new DefaultPluginRegistry(classLoader, instantiator)
+
+    public void canLoadPluginByType() {
+        def plugin = new TestPlugin2()
+
+        when:
+        def result = pluginRegistry.loadPlugin(TestPlugin2.class)
+
+        then:
+        result == plugin
+
+        and:
+        1* instantiator.newInstance(TestPlugin2.class, new Object[0]) >> plugin
+    }
+
+    public void canLookupPluginTypeById() {
+        def url = writePluginProperties("somePlugin", TestPlugin1)
+
+        given:
+        _ * classLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
+        _ * classLoader.loadClass(TestPlugin1.name) >> TestPlugin1
+
+        expect:
+        pluginRegistry.getTypeForId("somePlugin") == TestPlugin1
+    }
+
+    public void failsForUnknownId() {
+        when:
+        pluginRegistry.getTypeForId("unknownId")
+
+        then:
+        UnknownPluginException e = thrown()
+        e.message == "Plugin with id 'unknownId' not found."
+    }
+
+    public void failsWhenClassDoesNotImplementPlugin() {
+        when:
+        pluginRegistry.loadPlugin((Class)String.class)
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot create plugin of type 'String' as it does not implement the Plugin interface."
+    }
+
+    public void failsWhenNoImplementationClassSpecifiedInPropertiesFile() {
+        def properties = new Properties()
+        def propertiesFile = testDir.file("prop")
+        GUtil.saveProperties(properties, propertiesFile)
+        def url = propertiesFile.toURI().toURL()
+
+        given:
+        _ * classLoader.getResource("META-INF/gradle-plugins/noImpl.properties") >> url
+
+        when:
+        pluginRegistry.getTypeForId("noImpl")
+
+        then:
+        PluginInstantiationException e = thrown()
+        e.message == "No implementation class specified for plugin 'noImpl' in $url."
+    }
+
+    public void failsWhenImplementationClassSpecifiedInPropertiesFileDoesNotImplementPlugin() {
+        def properties = new Properties()
+        def propertiesFile = testDir.file("prop")
+        properties.setProperty("implementation-class", "java.lang.String")
+        GUtil.saveProperties(properties, propertiesFile)
+        def url = propertiesFile.toURI().toURL()
+
+        given:
+        _ * classLoader.getResource("META-INF/gradle-plugins/brokenImpl.properties") >> url
+        _ * classLoader.loadClass("java.lang.String") >> String
+
+        when:
+        pluginRegistry.getTypeForId("brokenImpl")
+
+        then:
+        PluginInstantiationException e = thrown()
+        e.message == "Implementation class 'java.lang.String' specified for plugin 'brokenImpl' does not implement the Plugin interface. Specified in $url."
+    }
+
+    public void wrapsPluginInstantiationFailure() {
+        def failure = new RuntimeException();
+
+        given:
+        _ * instantiator.newInstance(BrokenPlugin, new Object[0]) >> { throw new ObjectInstantiationException(BrokenPlugin.class, failure) }
+
+        when:
+        pluginRegistry.loadPlugin(BrokenPlugin.class)
+
+        then:
+        PluginInstantiationException e = thrown()
+        e.message == "Could not create plugin of type 'BrokenPlugin'."
+        e.cause == failure
+    }
+
+    public void wrapsFailureToLoadImplementationClass() {
+        def url = writePluginProperties("somePlugin", TestPlugin1)
+
+        given:
+        _ * classLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
+        _ * classLoader.loadClass(TestPlugin1.name) >> { throw new ClassNotFoundException() }
+
+        when:
+        pluginRegistry.getTypeForId("somePlugin")
+
+        then:
+        PluginInstantiationException e = thrown()
+        e.message == "Could not find implementation class '$TestPlugin1.name' for plugin 'somePlugin' specified in $url."
+        e.cause instanceof ClassNotFoundException
+    }
+
+    public void childUsesItsOwnInstantiatorToCreatePlugin() {
+        ClassLoader childClassLoader = Mock()
+        Instantiator childInstantiator = Mock()
+        def plugin = new TestPlugin1()
+
+        given:
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+
+        when:
+        def result = child.loadPlugin(TestPlugin1)
+
+        then:
+        result == plugin
+
+        and:
+        1 * childInstantiator.newInstance(TestPlugin1, new Object[0]) >> plugin
+        0 * instantiator._
+    }
+
+    public void childDelegatesToParentRegistryToLookupPluginType() throws Exception {
+        ClassLoader childClassLoader = Mock()
+        Instantiator childInstantiator = Mock()
+        def url = writePluginProperties("somePlugin", TestPlugin1)
+
+        given:
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+        _ * classLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
+        _ * classLoader.loadClass(TestPlugin1.name) >> TestPlugin1
+
+        when:
+        def type = child.getTypeForId("somePlugin")
+
+        then:
+        type == TestPlugin1
+
+        and:
+        0 * childClassLoader._
+    }
+
+    public void childClasspathCanContainAdditionalMappingsForPlugins() throws Exception {
+        ClassLoader childClassLoader = Mock()
+        Instantiator childInstantiator = Mock()
+        def url = writePluginProperties("somePlugin", TestPlugin1)
+
+        given:
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader, childInstantiator)
+        _ * childClassLoader.getResource("META-INF/gradle-plugins/somePlugin.properties") >> url
+        _ * childClassLoader.loadClass(TestPlugin1.name) >> TestPlugin1
+
+        when:
+        def type = child.getTypeForId("somePlugin")
+
+        then:
+        type == TestPlugin1
+    }
+
+    private URL writePluginProperties(String pluginId, Class implClass) {
+        def props = new Properties()
+        props.setProperty("implementation-class", implClass.name)
+        def propertiesFile = testDir.file("${pluginId}.properties")
+        propertiesFile.getParentFile().mkdirs()
+        GUtil.saveProperties(props, propertiesFile)
+        return propertiesFile.toURI().toURL()
+    }
+
+    private class BrokenPlugin implements Plugin<String> {
+        public void apply(String target) {
+        }
+    }
+
+}
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
deleted file mode 100644
index b119351..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
+++ /dev/null
@@ -1,256 +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.plugins;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Plugin;
-import org.gradle.api.internal.project.TestPlugin1;
-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.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Properties;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultPluginRegistryTest {
-    private String pluginId = "test";
-    private DefaultPluginRegistry pluginRegistry;
-    private JUnit4Mockery context = new JUnit4GroovyMockery();
-    @Rule
-    public TemporaryFolder testDir = new TemporaryFolder();
-    private ClassLoader classLoader;
-
-    @Before
-    public void setup() throws Exception {
-        classLoader = createClassLoader(pluginId, TestPlugin1.class.getName(), "parent");
-        pluginRegistry = new DefaultPluginRegistry(classLoader);
-    }
-
-    private ClassLoader createClassLoader(final String id, String implClass, String name) throws IOException {
-        TestFile classPath = testDir.createDir(name);
-        Properties props = new Properties();
-        props.setProperty("implementation-class", implClass);
-        final TestFile propertiesFile = classPath.file(id + ".properties");
-        propertiesFile.getParentFile().mkdirs();
-        GUtil.saveProperties(props, propertiesFile);
-        final ClassLoader classLoader = context.mock(ClassLoader.class, name);
-        context.checking(new Expectations() {{
-            allowing(classLoader).getResource("META-INF/gradle-plugins/" + id + ".properties");
-            will(returnValue(propertiesFile.toURI().toURL()));
-        }});
-        return classLoader;
-    }
-
-    @Test
-    public void canLoadPluginByType() {
-        assertThat(pluginRegistry.loadPlugin(TestPlugin2.class), instanceOf(TestPlugin2.class));
-    }
-
-    @Test
-    public void canLookupPluginTypeById() throws ClassNotFoundException {
-        expectClassLoaded(classLoader, TestPlugin1.class);
-
-        assertThat(pluginRegistry.getTypeForId(pluginId), equalTo((Class) TestPlugin1.class));
-    }
-
-    @Test
-    public void failsForUnknownId() {
-        expectResourceNotFound(classLoader, "unknownId");
-
-        try {
-            pluginRegistry.getTypeForId("unknownId");
-            fail();
-        } catch (UnknownPluginException e) {
-            assertThat(e.getMessage(), equalTo("Plugin with id 'unknownId' not found."));
-        }
-    }
-
-    @Test
-    public void failsWhenClassDoesNotImplementPlugin() {
-        try {
-            pluginRegistry.loadPlugin((Class)String.class);
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertThat(e.getMessage(), equalTo("Cannot create plugin of type 'String' as it does not implement the Plugin interface."));
-        }
-    }
-
-    @Test
-    public void failsWhenNoImplementationClassSpecifiedInPropertiesFile() throws MalformedURLException {
-        Properties properties = new Properties();
-        final TestFile propertiesFile = testDir.file("prop");
-        GUtil.saveProperties(properties, propertiesFile);
-        final URL url = propertiesFile.toURI().toURL();
-
-        context.checking(new Expectations() {{
-            allowing(classLoader).getResource("META-INF/gradle-plugins/noImpl.properties");
-            will(returnValue(url));
-        }});
-
-        try {
-            pluginRegistry.getTypeForId("noImpl");
-            fail();
-        } catch (PluginInstantiationException e) {
-            assertThat(e.getMessage(), equalTo("No implementation class specified for plugin 'noImpl' in " + url + "."));
-        }
-    }
-
-    @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);
-            fail();
-        } catch (PluginInstantiationException e) {
-            assertThat(e.getMessage(), equalTo("Could not create plugin of type 'BrokenPlugin'."));
-            assertThat(e.getCause(), Matchers.<Object>nullValue());
-        }
-    }
-
-    @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");
-
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
-        assertThat(child.loadPlugin(TestPlugin1.class), instanceOf(TestPlugin1.class));
-    }
-
-    @Test
-    public void childDelegatesToParentRegistryToLookupPluginType() throws Exception {
-        expectClassLoaded(classLoader, TestPlugin1.class);
-
-        ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
-
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
-        assertThat(child.getTypeForId(pluginId), equalTo((Class) pluginRegistry.getTypeForId(pluginId)));
-    }
-
-    @Test
-    public void childClasspathCanContainAdditionalMappingsForPlugins() throws Exception {
-        expectResourceNotFound(classLoader, "other");
-
-        ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
-        expectClassLoaded(childClassLoader, TestPlugin1.class);
-
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
-        assertThat(child.getTypeForId("other"), equalTo((Class) TestPlugin1.class));
-    }
-
-    @Test
-    public void parentIdMappingHasPrecedenceOverChildIdMapping() throws Exception {
-        expectClassLoaded(classLoader, TestPlugin1.class);
-        ClassLoader childClassLoader = createClassLoader(pluginId, "no-such-class", "child");
-
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
-        assertThat(child.getTypeForId(pluginId), equalTo((Class) pluginRegistry.getTypeForId(pluginId)));
-    }
-
-    @Test
-    public void childClasspathCanContainAdditionalPlugins() throws Exception {
-        expectClassesNotFound(classLoader);
-        expectResourceNotFound(classLoader, "other");
-
-        ClassLoader childClassLoader = createClassLoader("other", TestPlugin2.class.getName(), "child");
-        expectClassLoaded(childClassLoader, TestPlugin2.class);
-
-        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
-        assertThat(child.getTypeForId("other"), equalTo((Class) TestPlugin2.class));
-    }
-
-    private void expectResourceNotFound(final ClassLoader classLoader, final String id) {
-        context.checking(new Expectations(){{
-            allowing(classLoader).getResource("META-INF/gradle-plugins/" + id + ".properties");
-            will(returnValue(null));
-        }});
-    }
-
-    private void expectClassesNotFound(final ClassLoader classLoader) throws ClassNotFoundException {
-        context.checking(new Expectations() {{
-            allowing(classLoader).loadClass(with(notNullValue(String.class)));
-            will(throwException(new ClassNotFoundException()));
-        }});
-    }
-
-    private void expectClassLoaded(final ClassLoader classLoader, final Class<?> pluginClass) throws ClassNotFoundException {
-        context.checking(new Expectations() {{
-            atLeast(1).of(classLoader).loadClass(pluginClass.getName());
-            will(returnValue(pluginClass));
-        }});
-    }
-
-    private class BrokenPlugin implements Plugin<String> {
-        public void apply(String target) {
-        }
-    }
-}
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 7e7bd37..ebf5826 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
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.project
 
 import ch.qos.logback.classic.Level
-import ch.qos.logback.core.AppenderBase
 import org.apache.tools.ant.BuildException
 import org.apache.tools.ant.Project
 import org.apache.tools.ant.taskdefs.ConditionTask
@@ -27,10 +26,11 @@ 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.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
 import org.gradle.util.DefaultClassLoaderFactory
-import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
@@ -43,19 +43,13 @@ class DefaultIsolatedAntBuilderTest {
     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)
+    @Rule public final ConfigureLogging logging = new ConfigureLogging(appender)
     private Collection<File> classpath
 
     @Before
     public void attachAppender() {
         classpath = registry.getClassPath("GROOVY").asFiles
-        helper.attachAppender()
-        helper.setLevel(Level.INFO);
-    }
-
-    @After
-    public void detachAppender() {
-        helper.detachAppender()
+        logging.setLevel(Level.INFO);
     }
 
     @Test
@@ -121,7 +115,7 @@ class DefaultIsolatedAntBuilderTest {
             echo('${message}')
         }
 
-        assertThat(appender.writer.toString(), equalTo('[[ant:echo] a message]'))
+        assertThat(appender.toString(), equalTo('[WARN [ant:echo] a message]'))
     }
 
     @Test
@@ -133,9 +127,9 @@ class DefaultIsolatedAntBuilderTest {
             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]'))
+        assertThat(appender.toString(), containsString('[INFO a jcl log message]'))
+        assertThat(appender.toString(), containsString('[INFO an slf4j log message]'))
+        assertThat(appender.toString(), containsString('[INFO a log4j log message]'))
     }
 
     @Test
@@ -212,17 +206,3 @@ class TestAntTask extends Task {
         org.apache.log4j.Logger.getLogger('ant-test').info("a log4j log message")
     }
 }
-
-class TestAppender<LoggingEvent> extends AppenderBase<LoggingEvent> {
-    final StringWriter writer = new StringWriter()
-
-    synchronized void doAppend(LoggingEvent e) {
-        append(e)
-    }
-
-    protected void append(LoggingEvent e) {
-        writer.append("[")
-        writer.append(e.formattedMessage)
-        writer.append("]")
-    }
-}
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
index 82c4f44..664b628 100755
--- 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
@@ -134,4 +134,9 @@ public class GlobalServicesRegistryTest {
     public void providesAFileSystem() {
         assertThat(registry.get(FileSystem.class), notNullValue());
     }
+
+    @Test
+    public void providesADocumentationRegistry() throws Exception {
+        assertThat(registry.get(DocumentationRegistry.class), instanceOf(DocumentationRegistry.class));
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy
new file mode 100644
index 0000000..b7026e4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.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.internal.project;
+
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.GradleInternal
+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.TaskGraphExecuter
+import org.gradle.execution.taskgraph.DefaultTaskGraphExecuter
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.listener.ListenerManager
+import org.gradle.util.MultiParentClassLoader
+import spock.lang.Specification
+
+import static org.hamcrest.Matchers.sameInstance
+
+public class GradleInternalServiceRegistryTest extends Specification {
+    private GradleInternal gradle = Mock()
+    private ServiceRegistry parent = Mock()
+    private ListenerManager listenerManager = Mock()
+    private CacheRepository cacheRepository = Mock()
+    private GradleInternalServiceRegistry registry = new GradleInternalServiceRegistry(parent, gradle)
+    private StartParameter startParameter = new StartParameter()
+
+    public void setup() {
+        parent.get(ListenerManager.class) >> listenerManager
+        parent.get(CacheRepository.class) >> cacheRepository
+        parent.get(DocumentationRegistry) >> Mock(DocumentationRegistry)
+        gradle.getStartParameter() >> startParameter
+        gradle.getScriptClassLoader() >> new MultiParentClassLoader()
+    }
+
+    def "can create services for a project instance"() {
+        ProjectInternal project = Mock()
+
+        when:
+        ServiceRegistryFactory serviceRegistry = registry.createFor(project)
+
+        then:
+        serviceRegistry instanceof ProjectInternalServiceRegistry
+    }
+
+    def "provides a project registry"() {
+        when:
+        def projectRegistry = registry.get(IProjectRegistry)
+        def secondRegistry = registry.get(IProjectRegistry)
+
+        then:
+        projectRegistry instanceof DefaultProjectRegistry
+        projectRegistry sameInstance(secondRegistry)
+    }
+
+    def "provides a plugin registry"() {
+        when:
+        def pluginRegistry = registry.get(PluginRegistry)
+        def secondRegistry = registry.get(PluginRegistry)
+
+        then:
+        pluginRegistry instanceof DefaultPluginRegistry
+        secondRegistry sameInstance(pluginRegistry)
+    }
+
+    def "provides a build executer"() {
+        when:
+        def buildExecuter = registry.get(BuildExecuter)
+        def secondExecuter = registry.get(BuildExecuter)
+
+        then:
+        buildExecuter instanceof DefaultBuildExecuter
+        buildExecuter sameInstance(secondExecuter)
+    }
+
+    def "provides a task graph executer"() {
+        when:
+        def graphExecuter = registry.get(TaskGraphExecuter)
+        def secondExecuter = registry.get(TaskGraphExecuter)
+
+        then:
+        graphExecuter instanceof DefaultTaskGraphExecuter
+        graphExecuter sameInstance(secondExecuter)
+    }
+}
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
deleted file mode 100644
index 5e6485c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
+++ /dev/null
@@ -1,108 +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.StartParameter;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.internal.GradleInternal;
-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;
-import org.gradle.util.MultiParentClassLoader;
-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.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class GradleInternalServiceRegistryTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-    private final GradleInternalServiceRegistry registry = new GradleInternalServiceRegistry(parent, gradle);
-    private final StartParameter startParameter = new StartParameter();
-    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(parent).get(ListenerManager.class);
-            will(returnValue(listenerManager));
-            allowing(gradle).getStartParameter();
-            will(returnValue(startParameter));
-            allowing(gradle).getScriptClassLoader();
-            will(returnValue(new MultiParentClassLoader()));
-        }});
-    }
-
-    @Test
-    public void canCreateServicesForAProjectInstance() {
-        ProjectInternal project = context.mock(ProjectInternal.class);
-        ServiceRegistryFactory serviceRegistry = registry.createFor(project);
-        assertThat(serviceRegistry, instanceOf(ProjectInternalServiceRegistry.class));
-    }
-
-    @Test
-    public void providesAProjectRegistry() {
-        assertThat(registry.get(IProjectRegistry.class), instanceOf(DefaultProjectRegistry.class));
-        assertThat(registry.get(IProjectRegistry.class), sameInstance(registry.get(IProjectRegistry.class)));
-    }
-
-    @Test
-    public void providesAPluginRegistry() {
-        assertThat(registry.get(PluginRegistry.class), instanceOf(DefaultPluginRegistry.class));
-        assertThat(registry.get(PluginRegistry.class), sameInstance(registry.get(PluginRegistry.class)));
-    }
-
-    @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);
-            will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
-            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)));
-    }
-}
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 e615e59..e2d5431 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
@@ -41,9 +41,13 @@ import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.PluginContainer;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -73,6 +77,7 @@ public class ProjectInternalServiceRegistryTest {
     private final Factory publishServicesFactory = context.mock(Factory.class);
     private final DependencyHandler dependencyHandler = context.mock(DependencyHandler.class);
     private final ArtifactHandler artifactHandler = context.mock(ArtifactHandler.class);
+    private final DirectInstantiator instantiator = new DirectInstantiator();
 
     @Before
     public void setUp() {
@@ -91,9 +96,11 @@ public class ProjectInternalServiceRegistryTest {
             allowing(parent).get(DependencyManagementServices.class);
             will(returnValue(dependencyManagementServices));
             allowing(parent).get(org.gradle.internal.reflect.Instantiator.class);
-            will(returnValue(new org.gradle.internal.reflect.DirectInstantiator()));
+            will(returnValue(instantiator));
             allowing(parent).get(FileSystem.class);
             will(returnValue(context.mock(FileSystem.class)));
+            allowing(parent).get(ClassGenerator.class);
+            will(returnValue(context.mock(ClassGenerator.class)));
         }});
     }
 
@@ -105,6 +112,14 @@ public class ProjectInternalServiceRegistryTest {
 
     @Test
     public void providesATaskContainerFactory() {
+        final ITaskFactory childFactory = context.mock(ITaskFactory.class);
+
+        context.checking(new Expectations() {{
+            Matcher matcher = instanceOf(ClassGeneratorBackedInstantiator.class);
+            one(taskFactory).createChild(with(sameInstance(project)), with((Matcher<Instantiator>)matcher));
+            will(returnValue(childFactory));
+        }});
+
         assertThat(registry.getFactory(TaskContainerInternal.class), instanceOf(DefaultTaskContainerFactory.class));
     }
 
@@ -112,7 +127,8 @@ public class ProjectInternalServiceRegistryTest {
     public void providesAPluginContainer() {
         expectScriptClassLoaderProviderCreated();
         context.checking(new Expectations() {{
-            one(pluginRegistry).createChild(with(notNullValue(ClassLoader.class)));
+            Matcher matcher = Matchers.instanceOf(DependencyInjectingInstantiator.class);
+            one(pluginRegistry).createChild(with(notNullValue(ClassLoader.class)), with((Matcher<Instantiator>)matcher));
         }});
 
         assertThat(registry.get(PluginContainer.class), instanceOf(DefaultProjectsPluginContainer.class));
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
index 4d5365d..8612aac 100644
--- 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
@@ -30,28 +30,25 @@ 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.concurrent.DefaultExecutorFactory
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.listener.DefaultListenerManager
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.LoggingManagerInternal
-import org.gradle.internal.concurrent.DefaultExecutorFactory
-import org.gradle.internal.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.gradle.api.internal.*
-import org.gradle.initialization.*
-import org.gradle.internal.reflect.Instantiator
 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
@@ -60,7 +57,6 @@ import static org.junit.Assert.assertThat
 public class TopLevelBuildServiceRegistryTest extends Specification {
     @Rule
     TemporaryFolder tmpDir = new TemporaryFolder()
-    JUnit4Mockery context = new JUnit4GroovyMockery()
     StartParameter startParameter = new StartParameter()
     ServiceRegistry parent = Mock()
     Factory<CacheFactory> cacheFactoryFactory = Mock()
@@ -77,6 +73,7 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
         parent.getFactory(LoggingManagerInternal) >> Mock(Factory)
         parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
         parent.get(PluginModuleRegistry) >> Mock(PluginModuleRegistry)
+        parent.get(Instantiator) >> ThreadGlobalInstantiator.getOrCreate()
     }
 
     def delegatesToParentForUnknownService() {
@@ -166,7 +163,8 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
         setup:
         expectListenerManagerCreated()
         expect:
-        assertThat(registry.get(ExceptionAnalyser), instanceOf(DefaultExceptionAnalyser))
+        assertThat(registry.get(ExceptionAnalyser), instanceOf(MultipleBuildFailuresExceptionAnalyser))
+        assertThat(registry.get(ExceptionAnalyser).delegate, instanceOf(DefaultExceptionAnalyser))
         assertThat(registry.get(ExceptionAnalyser), sameInstance(registry.get(ExceptionAnalyser)))
     }
 
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 035b96e..e4f5840 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
@@ -23,7 +23,6 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.AbstractTask;
 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.*;
 import org.jmock.Expectations;
@@ -51,7 +50,6 @@ public class AnnotationProcessingTaskFactoryTest {
     }};
 
     private final ITaskFactory delegate = context.mock(ITaskFactory.class);
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
     private final Map args = new HashMap();
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
@@ -90,11 +88,11 @@ public class AnnotationProcessingTaskFactoryTest {
 
     private <T extends Task> T expectTaskCreated(final T task) {
         context.checking(new Expectations() {{
-            one(delegate).createTask(project, args);
+            one(delegate).createTask(args);
             will(returnValue(task));
         }});
 
-        assertThat(factory.createTask(project, args), sameInstance((Object) task));
+        assertThat(factory.createTask(args), sameInstance((Object) task));
         return task;
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java
index c99bf87..d20ead2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java
@@ -17,17 +17,17 @@ package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.TaskInputs;
-import static org.gradle.util.GUtil.*;
-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 static org.gradle.util.GUtil.map;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
 @RunWith(JMock.class)
 public class DependencyAutoWireTaskFactoryTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
@@ -37,12 +37,11 @@ public class DependencyAutoWireTaskFactoryTest {
     @Test
     public void addsDependencyOnInputFiles() {
         final TaskInternal task = context.mock(TaskInternal.class);
-        final ProjectInternal project = context.mock(ProjectInternal.class);
         final TaskInputs taskInputs = context.mock(TaskInputs.class);
         final FileCollection inputFiles = context.mock(FileCollection.class);
 
         context.checking(new Expectations() {{
-            one(delegate).createTask(project, map());
+            one(delegate).createTask(map());
             will(returnValue(task));
             allowing(task).getInputs();
             will(returnValue(taskInputs));
@@ -51,6 +50,6 @@ public class DependencyAutoWireTaskFactoryTest {
             one(task).dependsOn(inputFiles);
         }});
 
-        assertThat(factory.createTask(project, map()), sameInstance(task));
+        assertThat(factory.createTask(map()), sameInstance(task));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.groovy
new file mode 100644
index 0000000..eb83288
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.taskfactory
+
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Task
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.tasks.TaskInstantiationException
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.reflect.ObjectInstantiationException
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class TaskFactoryTest extends Specification {
+    final ClassGenerator generator = Mock()
+    final Instantiator instantiator = Mock()
+    final ProjectInternal project = HelperUtil.createRootProject()
+    final ITaskFactory taskFactory = new TaskFactory(generator).createChild(project, instantiator)
+
+    def setup() {
+        _ * generator.generate(_) >> { Class type -> type }
+        _ * instantiator.newInstance(_) >> { args -> args[0].newInstance() }
+    }
+
+    public void testUsesADefaultTaskTypeWhenNoneSpecified() {
+        when:
+        Task task = taskFactory.createTask([name: "task"]);
+
+        then:
+        task instanceof DefaultTask
+    }
+
+    public void injectsProjectAndNameIntoTask() {
+        when:
+        Task task = taskFactory.createTask([name: "task"]);
+
+        then:
+        task.project == project
+        task.name == 'task'
+    }
+
+    public void testCannotCreateTaskWithNoName() {
+        when:
+        taskFactory.createTask([:])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "The task name must be provided."
+    }
+
+    public void testCreateTaskOfTypeWithNoArgsConstructor() {
+        when:
+        Task task = taskFactory.createTask([name: 'task', type: TestDefaultTask.class])
+
+        then:
+        task instanceof TestDefaultTask
+    }
+
+    public void instantiatesAnInstanceOfTheDecoratedTaskType() {
+        when:
+        Task task = taskFactory.createTask([name: 'task', type: TestDefaultTask.class])
+
+        then:
+        task instanceof DecoratedTask
+
+        and:
+        1 * generator.generate(TestDefaultTask) >> DecoratedTask
+        1 * instantiator.newInstance(DecoratedTask) >> { new DecoratedTask() }
+        0 * _._
+    }
+
+    public void testCreateTaskWithDependencies() {
+        when:
+        Task task = taskFactory.createTask([name: 'task', dependsOn: "/path1"])
+
+        then:
+        task.dependsOn == ["/path1"] as Set
+    }
+
+    public void testCreateTaskWithAction() {
+        Action<Task> action = Mock()
+
+        when:
+        Task task = taskFactory.createTask([name: 'task', action: action])
+
+        then:
+        task.actions.size() == 1
+        task.actions[0].action == action
+    }
+
+    public void testCreateTaskWithActionClosure() {
+        Closure cl = Mock()
+
+        when:
+        Task task = taskFactory.createTask([name: 'task', action: cl])
+
+        then:
+        task.actions.size() == 1
+        task.actions[0].closure == cl
+    }
+
+    public void testCreateTaskForTypeWhichDoesNotImplementTask() {
+        when:
+        taskFactory.createTask([name: 'task', type:  NotATask])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot create task of type 'NotATask' as it does not implement the Task interface."
+    }
+
+    public void wrapsFailureToCreateTaskInstance() {
+        def failure = new RuntimeException()
+
+        when:
+        taskFactory.createTask([name: 'task', type:  TestDefaultTask])
+
+        then:
+        TaskInstantiationException e = thrown()
+        e.message == "Could not create task of type 'TestDefaultTask'."
+        e.cause == failure
+
+        and:
+        _ * instantiator.newInstance(TestDefaultTask) >> { throw new ObjectInstantiationException(TestDefaultTask, failure) }
+    }
+
+    public void createTaskWithDescription() {
+        when:
+        Task task = taskFactory.createTask([name: 'task', description: "some task"])
+
+        then:
+        task.description == "some task"
+    }
+
+    public void createTaskWithGroup() {
+        when:
+        Task task = taskFactory.createTask([name: 'task', group: "some group"])
+
+        then:
+        task.group == "some group"
+    }
+
+    public static class TestDefaultTask extends DefaultTask {
+    }
+
+    public static class DecoratedTask extends TestDefaultTask {
+    }
+
+    public static class NotATask {
+    }
+}
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
deleted file mode 100644
index 1f4d61c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
+++ /dev/null
@@ -1,233 +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.taskfactory;
-
-import org.gradle.api.*;
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.project.DefaultProject;
-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;
-
-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.*;
-
-/**
- * @author Hans Dockter
- */
-public class TaskFactoryTest {
-    public static final String TEST_TASK_NAME = "task";
-
-    private Map empyArgMap;
-
-    private TaskFactory taskFactory;
-
-    private DefaultProject testProject;
-
-    @Before
-    public void setUp() {
-        taskFactory = new TaskFactory(new AsmBackedClassGenerator());
-        testProject = HelperUtil.createRootProject();
-        empyArgMap = new HashMap();
-    }
-
-    @Test
-    public void testCreateTask() {
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task")));
-        assertThat(task, instanceOf(DefaultTask.class));
-        assertThat(task.getProject(), sameInstance((Project) testProject));
-        assertThat(task.getName(), equalTo("task"));
-        assertTrue(task.getActions().isEmpty());
-    }
-
-    @Test
-    public void testCannotCreateTaskWithNoName() {
-        try {
-            taskFactory.createTask(testProject, empyArgMap);
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertThat(e.getMessage(), equalTo("The task name must be provided."));
-        }
-    }
-
-    @Test
-    public void testCreateTaskWithDependencies() {
-        List testDependsOn = WrapUtil.toList("/path1");
-        Set expected = WrapUtil.toSet((Object) testDependsOn);
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DEPENDS_ON, testDependsOn)));
-        assertEquals(expected, task.getDependsOn());
-        assertTrue(task.getActions().isEmpty());
-    }
-
-    @Test
-    public void testCreateTaskWithSingleDependency() {
-        String testDependsOn = "/path1";
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DEPENDS_ON, testDependsOn)));
-        assertEquals(WrapUtil.toSet(testDependsOn), task.getDependsOn());
-        assertTrue(task.getActions().isEmpty());
-    }
-
-    @Test
-    public void testCreateTaskOfTypeWithNoArgsConstructor() {
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestDefaultTask.class)));
-        assertThat(task.getProject(), sameInstance((Project) testProject));
-        assertThat(task.getName(), equalTo("task"));
-        assertTrue(TestDefaultTask.class.isAssignableFrom(task.getClass()));
-    }
-
-    @Test
-    public void testAppliesConventionMappingToEachGetter() {
-        TestConventionTask task = (TestConventionTask) checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestConventionTask.class)));
-
-        assertThat(task.getProperty(), nullValue());
-
-        task.getConventionMapping().map("property", new Callable<Object>() {
-            public Object call() throws Exception {
-                return "conventionValue";
-            }
-        });
-
-        assertThat(task.getProperty(), equalTo("conventionValue"));
-
-        task.setProperty("value");
-        assertThat(task.getProperty(), equalTo("value"));
-    }
-
-    @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 Callable<Object>() {
-            public Object call() throws Exception {
-                throw new UnsupportedOperationException();
-            }
-        });
-        assertThat(task.getDescription(), nullValue());
-    }
-
-    @Test
-    public void testCreateTaskWithAction() {
-        Action<Task> action = new Action<Task>() {
-            public void execute(Task task) {
-            }
-        };
-
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_ACTION, 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)));
-        assertThat(task.getActions().size(), equalTo(1));
-        assertThat(ReflectionUtil.getProperty(task.getActions().get(0), "closure"), sameInstance((Object) HelperUtil.TEST_CLOSURE));
-    }
-
-    @Test
-    public void testCreateTaskForTypeWithMissingConstructor() {
-        try {
-            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, MissingConstructorTask.class));
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertEquals(
-                    "Cannot create task of type 'MissingConstructorTask' as it does not have a public no-args constructor.",
-                    e.getMessage());
-        }
-    }
-
-    @Test
-    public void testCreateTaskForTypeWhichDoesNotImplementTask() {
-        try {
-            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, NotATask.class));
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertEquals("Cannot create task of type 'NotATask' as it does not implement the Task interface.",
-                    e.getMessage());
-        }
-    }
-
-    @Test
-    public void testCreateTaskWhenConstructorThrowsException() {
-        try {
-            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, CannotConstructTask.class));
-            fail();
-        } catch (TaskInstantiationException e) {
-            assertEquals("Could not create task of type 'CannotConstructTask'.", e.getMessage());
-            assertTrue(RuntimeException.class.isInstance(e.getCause()));
-            assertEquals("fail", e.getCause().getMessage());
-        }
-    }
-
-    @Test
-    public void createTaskWithDescription() {
-        Object testDescription = 9;
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DESCRIPTION, testDescription)));
-        assertEquals("9", task.getDescription());
-    }
-
-    @Test
-    public void createTaskWithGroup() {
-        Object testGroup = "The Group";
-        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_GROUP, testGroup)));
-        assertEquals(testGroup, task.getGroup());
-    }
-
-    private Task checkTask(Task task) {
-        assertEquals(TEST_TASK_NAME, task.getName());
-        assertSame(testProject, task.getProject());
-        return task;
-    }
-
-    public static class TestDefaultTask extends DefaultTask {
-    }
-
-    public static class TestConventionTask extends ConventionTask {
-        private String property;
-
-        public String getProperty() {
-            return property;
-        }
-
-        public void setProperty(String property) {
-            this.property = property;
-        }
-    }
-
-    public static class MissingConstructorTask extends DefaultTask {
-        public MissingConstructorTask(Integer something) {
-        }
-    }
-
-    public static class NotATask {
-    }
-
-    public static class CannotConstructTask extends DefaultTask {
-        public CannotConstructTask() {
-            throw new RuntimeException("fail");
-        }
-    }
-}
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 a4c5c26..41d9a07 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
@@ -52,7 +52,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         assertThat(container.add(options), sameInstance(task));
@@ -65,7 +65,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         assertThat(container.add("task"), sameInstance(task));
@@ -77,7 +77,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         assertThat(container.add("task", Task.class), sameInstance(task));
@@ -90,7 +90,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
             one(task).configure(action);
             will(returnValue(task));
@@ -104,7 +104,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         assertThat(container.replace("task"), sameInstance(task));
@@ -117,7 +117,7 @@ public class DefaultTaskContainerTest {
         final Task task = task("task");
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         assertThat(container.replace("task", Task.class), sameInstance(task));
@@ -132,7 +132,7 @@ public class DefaultTaskContainerTest {
         container.addRule(rule);
 
         context.checking(new Expectations(){{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
 
@@ -144,7 +144,7 @@ public class DefaultTaskContainerTest {
         final Task task = addTask("task");
 
         context.checking(new Expectations() {{
-            one(taskFactory).createTask(project, singletonMap(Task.TASK_NAME, "task"));
+            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "task"));
             will(returnValue(task("task")));
         }});
 
@@ -164,7 +164,7 @@ public class DefaultTaskContainerTest {
 
         final Task newTask = task("task");
         context.checking(new Expectations() {{
-            one(taskFactory).createTask(project, singletonMap(Task.TASK_NAME, "task"));
+            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "task"));
             will(returnValue(newTask));
         }});
         
@@ -290,7 +290,7 @@ public class DefaultTaskContainerTest {
         final Task task = task(name);
         final Map<String, ?> options = singletonMap(Task.TASK_NAME, name);
         context.checking(new Expectations() {{
-            one(taskFactory).createTask(project, options);
+            one(taskFactory).createTask(options);
             will(returnValue(task));
         }});
         container.add(name);
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
index 1a28b48..abce164 100755
--- 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
@@ -193,7 +193,7 @@ public class DefaultJavaForkOptionsTest {
     }
 
     @Test
-    public void debugIsUpdatedWhenSetUsingJvmArgs() {
+    public void debugIsEnabledWhenSetUsingJvmArgs() {
         options.jvmArgs('-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005')
         assertTrue(options.debug)
         assertThat(options.jvmArgs, equalTo([]))
@@ -201,6 +201,7 @@ public class DefaultJavaForkOptionsTest {
         options.allJvmArgs = []
         assertFalse(options.debug)
 
+        options.debug = false
         options.jvmArgs = ['-Xdebug']
         assertFalse(options.debug)
         assertThat(options.jvmArgs, equalTo(['-Xdebug']))
@@ -213,6 +214,7 @@ public class DefaultJavaForkOptionsTest {
         assertTrue(options.debug)
         assertThat(options.jvmArgs, equalTo([]))
 
+        options.debug = false
         options.jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=other']
         assertFalse(options.debug)
         assertThat(options.jvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=other']))
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/logging/LoggingTest.java b/subprojects/core/src/test/groovy/org/gradle/api/logging/LoggingTest.java
index f974e09..8203cac 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/logging/LoggingTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/logging/LoggingTest.java
@@ -20,7 +20,7 @@ import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.spi.ILoggingEvent;
 import ch.qos.logback.classic.spi.LoggingEvent;
 import ch.qos.logback.core.Appender;
-import org.gradle.logging.LoggingTestHelper;
+import org.gradle.logging.ConfigureLogging;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -40,16 +40,16 @@ import static org.junit.Assert.*;
 public class LoggingTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final Appender<ILoggingEvent> appender = context.mock(Appender.class);
-    private final LoggingTestHelper helper = new LoggingTestHelper(appender);
+    private final ConfigureLogging logging = new ConfigureLogging(appender);
 
     @Before
     public void attachAppender() {
-        helper.attachAppender();
+        logging.attachAppender();
     }
 
     @After
     public void detachAppender() {
-        helper.detachAppender();
+        logging.detachAppender();
     }
 
     @Test
@@ -83,7 +83,7 @@ public class LoggingTest {
 
     @Test
     public void delegatesLevelIsEnabledToSlf4j() {
-        helper.setLevel(Level.WARN);
+        logging.setLevel(Level.WARN);
 
         Logger logger = Logging.getLogger(LoggingTest.class);
         assertTrue(logger.isErrorEnabled());
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java
deleted file mode 100644
index ad0e701..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java
+++ /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.api.specs;
-
-import org.gradle.util.WrapUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.gradle.util.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-abstract public class AbstractCompositeSpecTest {
-    private Spec spec1;
-    private Spec spec2;
-
-    public abstract CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs);
-
-    @Before
-    public void setUp() {
-        spec1 = new Spec<Object>() {
-            public boolean isSatisfiedBy(Object o) {
-                return false;
-            }
-        };
-        spec2 = new Spec<Object>() {
-            public boolean isSatisfiedBy(Object o) {
-                return false;
-            }
-        };
-    }
-
-    @Test
-    public void init() {
-        CompositeSpec<Object> compositeSpec = createCompositeSpec(spec1, spec2);
-        assertEquals(WrapUtil.toList(spec1, spec2), compositeSpec.getSpecs());
-    }
-
-    protected Spec<Object>[] createAtomicElements(boolean... satisfies) {
-        List<Spec<Object>> result = new ArrayList<Spec<Object>>();
-        for (final boolean satisfy : satisfies) {
-            result.add(new Spec<Object>() {
-                public boolean isSatisfiedBy(Object o) {
-                    return satisfy;
-                }
-            });
-        }
-        return result.toArray(new Spec[result.size()]);
-    }
-
-    @Test
-    public void equality() {
-        assertThat(createCompositeSpec(spec1), strictlyEqual(createCompositeSpec(spec1)));
-        assertFalse(createCompositeSpec(spec1).equals(createCompositeSpec(spec2)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
deleted file mode 100644
index 59ba97d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
+++ /dev/null
@@ -1,60 +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 static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.gradle.util.HelperUtil;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class AndSpecTest extends AbstractCompositeSpecTest {
-    public CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs) {
-        return new AndSpec<Object>(specs);
-    }
-
-    @Test
-    public void isSatisfiedWhenNoSpecs() {
-        assertTrue(new AndSpec<Object>().isSatisfiedBy(new Object()));
-    }
-    
-    @Test
-    public void isSatisfiedByWithAllTrue() {
-        assertTrue(new AndSpec<Object>(createAtomicElements(true, true, true)).isSatisfiedBy(new Object()));
-    }
-
-    @Test
-    public void isSatisfiedByWithOneFalse() {
-        assertFalse(new AndSpec<Object>(createAtomicElements(true, false, true)).isSatisfiedBy(new Object()));
-    }
-    
-    @Test
-    public void canAddSpecs() {
-        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
-        spec = spec.and(createAtomicElements(false));
-        assertFalse(spec.isSatisfiedBy(new Object()));
-    }
-    
-    @Test
-    public void canAddClosureAsASpec() {
-        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
-        spec = spec.and(HelperUtil.toClosure("{ false }"));
-        assertFalse(spec.isSatisfiedBy(new Object()));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
deleted file mode 100644
index 2ff25c7..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
+++ /dev/null
@@ -1,39 +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.specs;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class NotSpecTest {
-    @Test
-    public void testIsSatisfiedBy() {
-        assertThat(new NotSpec(createFilterSpec()).isSatisfiedBy(true), equalTo(false));
-        assertThat(new NotSpec(createFilterSpec()).isSatisfiedBy(false), equalTo(true));
-    }
-
-    private Spec<Boolean> createFilterSpec() {
-        return new Spec<Boolean>() {
-            public boolean isSatisfiedBy(Boolean element) {
-                return element;
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java b/subprojects/core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java
deleted file mode 100644
index 1b760cc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java
+++ /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.specs;
-
-import org.gradle.api.artifacts.Dependency;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class OrSpecTest extends AbstractCompositeSpecTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    public CompositeSpec createCompositeSpec(Spec... specs) {
-        return new OrSpec(specs);
-    }
-
-    @Test
-    public void isSatisfiedWhenNoSpecs() {
-        assertTrue(new OrSpec().isSatisfiedBy(new Object()));
-    }
-    
-    @Test
-    public void isSatisfiedByWithOneTrue() {
-        assertTrue(new OrSpec(createAtomicElements(false, true, false)).isSatisfiedBy(context.mock(Dependency.class)));
-    }
-
-    @Test
-    public void isSatisfiedByWithAllFalse() {
-        assertFalse(new AndSpec(createAtomicElements(false, false, false)).isSatisfiedBy(context.mock(Dependency.class)));
-    }
-}
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
index 5609e9a..a84e233 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.api.specs
 
-import spock.lang.Specification
 import spock.lang.Issue
+import spock.lang.Specification
 
 class SpecsTest extends Specification {
 
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 0e4a369..61a7581 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
@@ -37,7 +37,6 @@ public class AbstractCopyTaskTest extends AbstractTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         task = createTask(TestCopyTask.class);
         task.action = context.mock(CopyActionImpl.class);
         task.defaultSource = context.mock(FileCollection.class);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy
index 8d48ba2..9ac42c6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy
@@ -33,7 +33,6 @@ public class CopyTest extends AbstractTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp()
         context.setImposteriser(ClassImposteriser.INSTANCE)
         action = context.mock(FileCopyActionImpl.class)
         copyTask = createTask(Copy.class)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java
index 3bdd58b..45cc0d1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java
@@ -46,7 +46,6 @@ public class DeleteTest extends AbstractConventionTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         delete = createTask(Delete.class);
         DefaultFileOperations fileOperations = (DefaultFileOperations) ((DefaultProject)
                 delete.getProject()).getServices().get(FileOperations.class);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
index 1bcb1bb..c03c076 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
@@ -37,7 +37,6 @@ class DirectoryTest extends AbstractTaskTest {
     }
 
     @Before public void setUp() {
-        super.setUp()
         directoryForAbstractTest = createTask(Directory.class)
         directory = createTask(Directory.class, project, TASK_DIR_NAME)
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy
index cffb52f..77f6293 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy
@@ -33,7 +33,6 @@ public class ExecTest extends AbstractTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp()
         execTask = createTask(Exec.class)
         execTask.setExecAction(execAction)
     }
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
index 26a7c48..2e3f6e6 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
@@ -35,7 +35,6 @@ public class GradleBuildTest extends AbstractTaskTest {
 
     @Before
     void setUp() {
-        super.setUp()
         task = createTask(GradleBuild.class)
         GradleLauncher.injectCustomFactory(launcherFactoryMock)
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy
index 6922747..59aafd8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy
@@ -30,7 +30,6 @@ class SourceTaskTest extends AbstractTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp()
         task = createTask(SourceTask.class)
     }
     
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 6e9e2eb..12f33c3 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
@@ -17,6 +17,8 @@
 package org.gradle.api.tasks;
 
 import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishArtifactSet;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.file.FileCollection;
@@ -35,6 +37,7 @@ 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.*;
@@ -50,13 +53,13 @@ public class UploadTest extends AbstractTaskTest {
     private RepositoryHandler repositoriesMock;
     private ArtifactPublisher artifactPublisherMock;
     private ConfigurationInternal configurationMock;
+    private Module moduleMock;
 
     @Before public void setUp() {
-        super.setUp();
         upload = createTask(Upload.class);
         repositoriesMock = context.mock(RepositoryHandler.class);
         artifactPublisherMock = context.mock(ArtifactPublisher.class);
-
+        moduleMock = context.mock(Module.class);
         configurationMock = context.mock(ConfigurationInternal.class);
     }
 
@@ -77,7 +80,9 @@ public class UploadTest extends AbstractTaskTest {
         upload.setConfiguration(configurationMock);
         upload.setArtifactPublisher(artifactPublisherMock);
         context.checking(new Expectations() {{
-            one(artifactPublisherMock).publish(configurationMock, descriptorDestination);
+            one(configurationMock).getModule(); will(returnValue(moduleMock));
+            one(configurationMock).getHierarchy(); will(returnValue(Collections.emptySet()));
+            one(artifactPublisherMock).publish(moduleMock, Collections.<Configuration>emptySet(), descriptorDestination, null);
         }});
         upload.upload();
     }
@@ -88,7 +93,9 @@ public class UploadTest extends AbstractTaskTest {
         upload.setConfiguration(configurationMock);
         upload.setArtifactPublisher(artifactPublisherMock);
         context.checking(new Expectations() {{
-            one(artifactPublisherMock).publish(configurationMock, null);
+            one(configurationMock).getModule(); will(returnValue(moduleMock));
+            one(configurationMock).getHierarchy(); will(returnValue(Collections.emptySet()));
+            one(artifactPublisherMock).publish(moduleMock, Collections.<Configuration>emptySet(), null, null);
         }});
         upload.upload();
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy
index 95edced..c65aef8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy
@@ -28,7 +28,6 @@ class TarTest extends AbstractArchiveTaskTest {
     Tar tar
 
     @Before public void setUp()  {
-        super.setUp()
         tar = createTask(Tar)
         configure(tar)
         tar.from tmpDir.createFile('file.txt')
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy
index 980edcd..9ea31f0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy
@@ -27,7 +27,6 @@ class ZipTest extends AbstractArchiveTaskTest {
     Zip zip
 
     @Before public void setUp()  {
-        super.setUp()
         zip = createTask(Zip)
         configure(zip)
         zip.from tmpDir.createFile('file.txt')
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
deleted file mode 100644
index 24a613b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
+++ /dev/null
@@ -1,140 +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.diagnostics;
-
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-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.HelperUtil;
-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 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;
-
- at RunWith(JMock.class)
-public class DependencyReportTaskTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private Project project;
-    private DependencyReportTask task;
-
-    @Before
-    public void setup() {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
-        project = context.mock(ProjectInternal.class);
-
-        context.checking(new Expectations() {{
-            allowing(project).absoluteProjectPath("list");
-            will(returnValue(":path"));
-            allowing(project).getConvention();
-            will(returnValue(null));
-        }});
-
-        task = HelperUtil.createTask(DependencyReportTask.class);
-    }
-
-    @Test
-    public void init() {
-        assertThat(task.getRenderer(), instanceOf(AsciiReportRenderer.class));
-        assertThat(task.getConfigurations(), nullValue());
-    }
-
-    @Test
-    public void passesEachProjectConfigurationToRenderer() throws IOException {
-        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() {{
-            allowing(project).getConfigurations();
-            will(returnValue(configurationContainer));
-
-            allowing(configurationContainer).getAll();
-            will(returnValue(WrapUtil.toSet(configuration1, configuration2)));
-        }});
-        assertConfigurationsIsPassedToRenderer(configuration1, configuration2);
-    }
-
-    @Test
-    public void passesSpecifiedConfigurationToRenderer() throws IOException {
-        final Configuration configuration1 = context.mock(Configuration.class, "Configuration1");
-        final Configuration configuration2 = context.mock(Configuration.class, "Configuration2");
-        task.setConfigurations(WrapUtil.toSet(configuration1, configuration2));
-        assertConfigurationsIsPassedToRenderer(configuration1, configuration2);
-    }
-
-    private void assertConfigurationsIsPassedToRenderer(final Configuration configuration1, final Configuration configuration2) throws IOException {
-        final DependencyReportRenderer renderer = context.mock(DependencyReportRenderer.class);
-        final ResolvedConfiguration resolvedConfiguration1 = context.mock(ResolvedConfiguration.class, "ResolvedConf1");
-        final ResolvedConfiguration resolvedConfiguration2 = context.mock(ResolvedConfiguration.class, "ResolvedConf2");
-
-        task.setRenderer(renderer);
-        task.setConfigurations(WrapUtil.toSet(configuration1, configuration2));
-
-        context.checking(new Expectations() {{
-            allowing(configuration1).getName();
-            will(returnValue("config1"));
-
-            allowing(configuration2).getName();
-            will(returnValue("config2"));
-
-            Sequence resolve = context.sequence("resolve");
-            Sequence render = context.sequence("render");
-
-            one(configuration1).getResolvedConfiguration();
-            inSequence(resolve);
-            will(returnValue(resolvedConfiguration1));
-
-            one(renderer).startConfiguration(configuration1);
-            inSequence(render);
-
-            one(renderer).render(resolvedConfiguration1);
-            inSequence(render);
-
-            one(renderer).completeConfiguration(configuration1);
-            inSequence(render);
-
-            one(configuration2).getResolvedConfiguration();
-            inSequence(resolve);
-            will(returnValue(resolvedConfiguration2));
-
-            one(renderer).startConfiguration(configuration2);
-            inSequence(render);
-
-            one(renderer).render(resolvedConfiguration2);
-            inSequence(render);
-
-            one(renderer).completeConfiguration(configuration2);
-            inSequence(render);
-        }});
-        task.generate(project);
-    }
-
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.groovy
deleted file mode 100644
index a248575..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.groovy
+++ /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.tasks.diagnostics.internal
-
-class AggregateMultiProjectTaskReportModelTest extends TaskModelSpecification {
-    final AggregateMultiProjectTaskReportModel model = new AggregateMultiProjectTaskReportModel(false)
-
-    def mergesTheGroupsFromEachProject() {
-        TaskReportModel project1 = Mock()
-        TaskReportModel project2 = Mock()
-        _ * project1.groups >> (['p1', 'common'] as LinkedHashSet)
-        _ * project2.groups >> (['p2', 'common'] as LinkedHashSet)
-        _ * _.getTasksForGroup(_) >> ([taskDetails('task')] as Set)
-
-        when:
-        model.add(project1)
-        model.add(project2)
-        model.build()
-
-        then:
-        model.groups == ['p1', 'common', 'p2'] as Set
-    }
-
-    def mergesTheGroupsWithAGivenNameFromEachProjectIntoASingleGroup() {
-        TaskDetails task1 = taskDetails('task1')
-        TaskDetails task2 = taskDetails('task2')
-        TaskReportModel project1 = Mock()
-        TaskReportModel project2 = Mock()
-        _ * project1.groups >> (['group'] as LinkedHashSet)
-        _ * project1.getTasksForGroup('group') >> ([task1] as Set)
-        _ * project2.groups >> (['group'] as LinkedHashSet)
-        _ * project2.getTasksForGroup('group') >> ([task2] as Set)
-
-        when:
-        model.add(project1)
-        model.add(project2)
-        model.build()
-
-        then:
-        model.getTasksForGroup('group') == [task1, task2] as Set
-    }
-
-    def mergesTheTasksWithAGivenNameFromEachProjectIntoASingleTask() {
-        TaskDetails task1 = taskDetails(':task')
-        TaskDetails task2 = taskDetails(':other')
-        TaskDetails task3 = taskDetails(':sub:task')
-        TaskReportModel project1 = Mock()
-        TaskReportModel project2 = Mock()
-        _ * project1.groups >> (['group'] as LinkedHashSet)
-        _ * project1.getTasksForGroup('group') >> ([task1, task2] as Set)
-        _ * project2.groups >> (['group'] as LinkedHashSet)
-        _ * project2.getTasksForGroup('group') >> ([task3] as Set)
-
-        def model = new AggregateMultiProjectTaskReportModel(true)
-
-        when:
-        model.add(project1)
-        model.add(project2)
-        model.build()
-
-        then:
-        model.getTasksForGroup('group')*.path*.path as Set == ['task', 'other'] as Set
-    }
-
-    def handlesGroupWhichIsNotPresentInEachProject() {
-        TaskDetails task1 = taskDetails('task1')
-        TaskReportModel project1 = Mock()
-        TaskReportModel project2 = Mock()
-        _ * project1.groups >> (['group'] as LinkedHashSet)
-        _ * project1.getTasksForGroup('group') >> ([task1] as Set)
-        _ * project2.groups >> ([] as LinkedHashSet)
-        0 * project2.getTasksForGroup(_)
-
-        when:
-        model.add(project1)
-        model.add(project2)
-        model.build()
-
-        then:
-        model.getTasksForGroup('group') == [task1] as Set
-    }
-
-    def groupNamesAreCaseInsensitive() {
-        TaskDetails task1 = taskDetails('task1')
-        TaskDetails task2 = taskDetails('task2')
-        TaskReportModel project1 = Mock()
-        TaskReportModel project2 = Mock()
-        _ * project1.groups >> (['A'] as LinkedHashSet)
-        _ * project1.getTasksForGroup('A') >> ([task1] as Set)
-        _ * project2.groups >> (['a'] as LinkedHashSet)
-        _ * project2.getTasksForGroup('a') >> ([task2] as Set)
-
-        when:
-        model.add(project1)
-        model.add(project2)
-        model.build()
-
-        then:
-        model.groups == ['A'] as Set
-        model.getTasksForGroup('A') == [task1, task2] as Set
-    }
-}
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
deleted file mode 100644
index 0a08875..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.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.api.tasks.diagnostics.internal
-
-import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.logging.TestStyledTextOutput
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-import org.gradle.api.artifacts.ResolvedConfiguration
-import org.gradle.api.artifacts.ResolvedDependency
-
-class AsciiReportRendererTest extends Specification {
-    private final TestStyledTextOutput textOutput = new TestStyledTextOutput().ignoreStyle()
-    private final AsciiReportRenderer renderer = new AsciiReportRenderer()
-    private final Project project = HelperUtil.createRootProject()
-
-    def setup() {
-        renderer.output = textOutput
-    }
-
-    def writesMessageWhenProjectHasNoConfigurations() {
-        when:
-        renderer.startProject(project);
-        renderer.completeProject(project);
-
-        then:
-        textOutput.value.contains('No configurations')
-    }
-
-    def writesConfigurationHeader() {
-        Configuration configuration1 = Mock()
-        configuration1.getName() >> 'config1'
-        configuration1.getDescription() >> 'description'
-        Configuration configuration2 = Mock()
-        configuration2.getName() >> 'config2'
-
-        when:
-        renderer.startConfiguration(configuration1);
-        renderer.completeConfiguration(configuration1);
-        renderer.startConfiguration(configuration2);
-        renderer.completeConfiguration(configuration2);
-
-        then:
-        textOutput.value.readLines() == [
-                'config1 - description',
-                '',
-                'config2'
-        ]
-    }
-
-    def rendersDependencyTreeForConfiguration() {
-        ResolvedConfiguration resolvedConfig = Mock()
-        Configuration configuration = Mock()
-        ResolvedDependency dep1 = Mock()
-        ResolvedDependency dep11 = Mock()
-        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'
-
-        when:
-        renderer.startConfiguration(configuration)
-        renderer.render(resolvedConfig)
-
-        then:
-        textOutput.value.readLines() == [
-                'config',
-                '+--- dep1 [config1]',
-                '|    \\--- dep1.1 [config1.1]',
-                '\\--- dep2 [config2]',
-                '     +--- dep2.1 [config2.1]',
-                '     \\--- dep2.2 [config2.2]'
-        ]
-    }
-
-    def rendersDependencyTreeForEmptyConfiguration() {
-        ResolvedConfiguration configuration = Mock()
-        configuration.getFirstLevelModuleDependencies() >> {[] as Set}
-
-        when:
-        renderer.render(configuration)
-
-        then:
-        textOutput.value.readLines() == ['No dependencies']
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy
deleted file mode 100644
index ad28045..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy
+++ /dev/null
@@ -1,110 +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
-
-class DefaultGroupTaskReportModelTest extends TaskModelSpecification {
-    final TaskReportModel target = Mock()
-    final DefaultGroupTaskReportModel model = new DefaultGroupTaskReportModel()
-
-    def mergesDefaultGroupIntoOtherGroup() {
-        TaskDetails task1 = taskDetails('1')
-        TaskDetails task2 = taskDetails('2')
-        TaskDetails task3 = taskDetails('3')
-        _ * target.groups >> ['a', '', 'other']
-        _ * target.getTasksForGroup('a') >> [task1]
-        _ * target.getTasksForGroup('') >> [task2]
-        _ * target.getTasksForGroup('other') >> [task3]
-        
-        when:
-        model.build(target)
-
-        then:
-
-        model.groups as List == ['a', 'other']
-        model.getTasksForGroup('a') as List == [task1]
-        model.getTasksForGroup('other') as List == [task2, task3]
-    }
-
-    def groupNamesAreOrderedCaseInsensitive() {
-        TaskDetails task1 = taskDetails('task1')
-        TaskDetails task2 = taskDetails('task2')
-        TaskDetails task3 = taskDetails('task3')
-        TaskDetails task4 = taskDetails('task4')
-        TaskDetails task5 = taskDetails('task5')
-
-        _ * target.groups >> (['Abc', 'a', 'A', '', 'Other'] as LinkedHashSet)
-        _ * target.getTasksForGroup('a') >> [task1]
-        _ * target.getTasksForGroup('A') >> [task2]
-        _ * target.getTasksForGroup('Abc') >> [task3]
-        _ * target.getTasksForGroup('') >> [task4]
-        _ * target.getTasksForGroup('Other') >> [task5]
-
-        when:
-        model.build(target)
-
-        then:
-        model.groups as List == ['A', 'a', 'Abc', 'Other']
-        model.getTasksForGroup('Other') as List == [task4, task5]
-    }
-
-    def taskNamesAreOrderedCaseInsensitiveByNameThenPath() {
-        def task1 = taskDetails('task_A')
-        def task2 = taskDetails('a:task_A')
-        def task3 = taskDetails('a:a:task_A')
-        def task4 = taskDetails('B:task_A')
-        def task5 = taskDetails('c:task_A')
-        def task6 = taskDetails('b:task_a')
-        def task7 = taskDetails('a:task_Abc')
-        def task8 = taskDetails('task_b')
-        _ * target.groups >> ['group']
-        _ * target.getTasksForGroup('group') >> ([task6, task3, task7, task4, task5, task1, task8, task2] as LinkedHashSet)
-
-        when:
-        model.build(target)
-
-        then:
-        model.getTasksForGroup('group') as List == [task1, task2, task3, task4, task5, task6, task7, task8]
-    }
-
-    def renamesDefaultGroupWhenOtherGroupNotPresent() {
-        TaskDetails task1 = taskDetails('1')
-        TaskDetails task2 = taskDetails('2')
-        _ * target.groups >> ['a', '']
-        _ * target.getTasksForGroup('a') >> [task1]
-        _ * target.getTasksForGroup('') >> [task2]
-
-        when:
-        model.build(target)
-
-        then:
-        model.groups as List == ['a', 'other']
-        model.getTasksForGroup('a') as List == [task1]
-        model.getTasksForGroup('other') as List == [task2]
-    }
-
-    def doesNotRenameDefaultGroupWhenItIsTheOnlyGroup() {
-        TaskDetails task1 = taskDetails('1')
-        _ * target.groups >> ['']
-        _ * target.getTasksForGroup('') >> [task1]
-
-        when:
-        model.build(target)
-
-        then:
-        model.groups as List == ['']
-        model.getTasksForGroup('') as List == [task1]
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy
deleted file mode 100644
index 024b9cb..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy
+++ /dev/null
@@ -1,179 +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.util.Path
-
-class SingleProjectTaskReportModelTest extends TaskModelSpecification {
-    final TaskDetailsFactory factory = Mock()
-    final SingleProjectTaskReportModel model = new SingleProjectTaskReportModel(factory)
-
-    def setup() {
-        _ * factory.create(!null) >> {args ->
-            def task = args[0]
-            [getPath: { Path.path(task.path) }, getName: { task.name }] as TaskDetails
-        }
-    }
-
-    def groupsTasksBasedOnTheirGroup() {
-        def task1 = task('task1', 'group1')
-        def task2 = task('task2', 'group2')
-        def task3 = task('task3', 'group1')
-
-        when:
-        model.build([task1, task2, task3])
-
-        then:
-        model.groups == ['group1', 'group2'] as Set
-        model.getTasksForGroup('group1')*.task == [task1, task3]
-        model.getTasksForGroup('group2')*.task == [task2]
-    }
-
-    def groupsAreTreatedAsCaseInsensitive() {
-        def task1 = task('task1', 'a')
-        def task2 = task('task2', 'B')
-        def task3 = task('task3', 'b')
-        def task4 = task('task4', 'c')
-
-        when:
-        model.build([task2, task3, task4, task1])
-
-        then:
-        model.groups == ['a', 'B', 'c'] as Set
-        model.getTasksForGroup('a')*.task == [task1]
-        model.getTasksForGroup('B')*.task == [task2, task3]
-        model.getTasksForGroup('c')*.task == [task4]
-    }
-
-    def tasksWithNoGroupAreTreatedAsChildrenOfTheNearestTopLevelTaskTheyAreReachableFrom() {
-        def task1 = task('task1')
-        def task2 = task('task2')
-        def task3 = task('task3', 'group1', task1, task2)
-        def task4 = task('task4', task3, task1)
-        def task5 = task('task5', 'group2', task4)
-
-        when:
-        model.build([task1, task2, task3, task4, task5])
-
-        then:
-        TaskDetails task3Details = (model.getTasksForGroup('group1') as List).first()
-        task3Details.task == task3
-        task3Details.children*.task == [task1, task2]
-
-        TaskDetails task5Details = (model.getTasksForGroup('group2') as List).first()
-        task5Details.task == task5
-        task5Details.children*.task == [task4]
-    }
-
-    def theDependenciesOfATopLevelTaskAreTheUnionOfTheDependenciesOfItsChildren() {
-        def task1 = task('task1', 'group1')
-        def task2 = task('task2', 'group2', task1)
-        def task3 = task('task3', 'group3')
-        def task4 = task('task4', task2)
-        def task5 = task('task5', 'group4', task3, task4)
-
-        when:
-        model.build([task1, task2, task3, task4, task5])
-
-        then:
-        TaskDetails t = (model.getTasksForGroup('group4') as List).first()
-        t.dependencies*.path*.name as Set == ['task2', 'task3'] as Set
-    }
-
-    def dependenciesIncludeExternalTasks() {
-        def task1 = task('task1')
-        def task2 = task('task2', 'other')
-        def task3 = task('task3', 'group', task1, task2)
-
-        when:
-        model.build([task2, task3])
-
-        then:
-        TaskDetails t = (model.getTasksForGroup('group') as List).first()
-        t.dependencies*.path*.name as Set == ['task1', 'task2'] as Set
-    }
-
-    def dependenciesDoNotIncludeTheChildrenOfOtherTopLevelTasks() {
-        def task1 = task('task1')
-        def task2 = task('task2', 'group1', task1)
-        def task3 = task('task3', task1)
-        def task4 = task('task4', task2)
-        def task5 = task('task5', 'group2', task3, task4)
-
-        when:
-        model.build([task1, task2, task3, task4, task5])
-
-        then:
-        TaskDetails t = (model.getTasksForGroup('group2') as List).first()
-        t.dependencies*.path*.name as Set == ['task2'] as Set
-    }
-
-    def addsAGroupThatContainsTheTasksWithNoGroup() {
-        def task1 = task('task1')
-        def task2 = task('task2', 'group', task1)
-        def task3 = task('task3')
-        def task4 = task('task4', task2)
-        def task5 = task('task5', task3, task4)
-
-        when:
-        model.build([task1, task2, task3, task4, task5])
-
-        then:
-        model.groups == ['group', ''] as Set
-        def tasks = model.getTasksForGroup('') as List
-        tasks*.task == [task5]
-        def t = tasks.first()
-        t.task == task5
-        t.children*.task == [task3, task4]
-    }
-
-    def addsAGroupWhenThereAreNoTasksWithAGroup() {
-        def task1 = task('task1')
-        def task2 = task('task2', task1)
-        def task3 = task('task3')
-
-        when:
-        model.build([task1, task2, task3])
-
-        then:
-        model.groups == [''] as Set
-        def tasks = model.getTasksForGroup('') as List
-        tasks*.task == [task2, task3]
-    }
-
-    def buildsModelWhenThereAreNoTasks() {
-        when:
-        model.build([])
-
-        then:
-        model.groups as List == []
-    }
-
-    def ignoresReachableTasksOutsideTheProject() {
-        def other1 = task('other1')
-        def other2 = task('other2', other1)
-        def task1 = task('task1', other2)
-        def task2 = task('task2', 'group1', task1)
-
-        when:
-        model.build([task1, task2])
-
-        then:
-        TaskDetails t = (model.getTasksForGroup('group1') as List).first()
-        t.children*.task == [task1]
-        t.dependencies*.path*.name == ['other2']
-    }
-}
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
deleted file mode 100644
index f3cc046..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.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.api.tasks.diagnostics.internal
-
-import org.gradle.api.Task
-import org.gradle.api.Project
-import org.gradle.util.Path
-
-class TaskDetailsFactoryTest extends TaskModelSpecification {
-    final Project project = Mock()
-    final Project subproject = Mock()
-    final Task task = Mock()
-    TaskDetailsFactory factory
-
-    def setup() {
-        project.allprojects >> [project, subproject]
-        factory = new TaskDetailsFactory(project)
-    }
-    
-    def createsDetailsForTaskInMainProject() {
-        task.project >> project
-        task.path >> ':path'
-        project.relativeProjectPath(':path') >> 'task'
-
-        expect:
-        def details = factory.create(task)
-        details.path == Path.path('task')
-    }
-
-    def createsDetailsForTaskInSubProject() {
-        task.project >> subproject
-        task.path >> ':sub:path'
-        project.relativeProjectPath(':sub:path') >> 'sub:task'
-
-        expect:
-        def details = factory.create(task)
-        details.path == Path.path('sub:task')
-    }
-
-    def createsDetailsForTaskInOtherProject() {
-        Project other = Mock()
-        task.project >> other
-        task.path >> ':other:task'
-
-        expect:
-        def details = factory.create(task)
-        details.path == Path.path(':other:task')
-    }
-
-    def providesValuesForOtherProperties() {
-        task.project >> project
-        task.name >> 'task'
-        task.description >> 'description'
-
-        expect:
-        def details = factory.create(task)
-        details.description == 'description'
-        details.dependencies.isEmpty()
-        details.children.isEmpty()
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskModelSpecification.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskModelSpecification.groovy
deleted file mode 100644
index 7a1cbe6..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskModelSpecification.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.api.tasks.diagnostics.internal
-
-import spock.lang.Specification
-import org.gradle.api.tasks.TaskDependency
-import org.gradle.api.Task
-import org.gradle.util.Path
-
-class TaskModelSpecification extends Specification {
-    def taskDetails(String path) {
-        return taskDetails([:], path)
-    }
-
-    def taskDetails(Map properties, String path) {
-        TaskDetails task = Mock()
-        _ * task.path >> Path.path(path)
-        _ * task.toString() >> path
-        _ * task.description >> properties.description
-        _ * task.dependencies >> ((properties.dependencies ?: []) as Set)
-        return task
-    }
-
-    def task(String name, String group = null, Task... dependencies) {
-        Task task = Mock()
-        _ * task.toString() >> name
-        _ * task.name >> name
-        _ * task.path >> ":$name"
-        _ * task.group >> group
-        _ * task.compareTo(!null) >> { args -> name.compareTo(args[0].name) }
-        TaskDependency dep = Mock()
-        _ * dep.getDependencies(task) >> {dependencies as Set}
-        _ * task.taskDependencies >> dep
-        return task
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
deleted file mode 100644
index bf01b11..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
+++ /dev/null
@@ -1,184 +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.Rule
-import org.gradle.logging.TestStyledTextOutput
-
-/**
- * @author Hans Dockter
- */
-class TaskReportRendererTest extends TaskModelSpecification {
-    private final TestStyledTextOutput writer = new TestStyledTextOutput().ignoreStyle()
-    private final TaskReportRenderer renderer = new TaskReportRenderer()
-
-    def setup() {
-        renderer.output = writer
-    }
-
-    def writesTaskAndDependenciesWithDetailDisabled() {
-        TaskDetails task1 = taskDetails(':task1', description: 'task1Description')
-        TaskDetails task2 = taskDetails(':task2')
-        TaskDetails task3 = taskDetails(':task3')
-        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
-        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
-
-        List testDefaultTasks = ['task1', 'task2']
-
-        when:
-        renderer.showDetail(false)
-        renderer.addDefaultTasks(testDefaultTasks)
-        renderer.startTaskGroup('group')
-        renderer.addTask(task1)
-        renderer.addChildTask(task2)
-        renderer.addTask(task3)
-        renderer.completeTasks()
-        renderer.addRule(rule1)
-        renderer.addRule(rule2)
-
-        then:
-        writer.value == '''Default tasks: task1, task2
-
-Group tasks
------------
-:task1 - task1Description
-:task3
-
-Rules
------
-rule1Description
-rule2Description
-'''
-    }
-
-    def writesTaskAndDependenciesWithDetail() {
-        TaskDetails task11 = taskDetails(':task11')
-        TaskDetails task12 = taskDetails(':task12')
-        TaskDetails task1 = taskDetails(':task1', description: 'task1Description', dependencies: [task11, task12])
-        TaskDetails task2 = taskDetails(':task2')
-        TaskDetails task3 = taskDetails(':task3', dependencies: [task1])
-        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
-        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
-        List testDefaultTasks = ['task1', 'task2']
-
-        when:
-        renderer.showDetail(true)
-        renderer.addDefaultTasks(testDefaultTasks)
-        renderer.startTaskGroup('group')
-        renderer.addTask(task1)
-        renderer.addChildTask(task2)
-        renderer.addTask(task3)
-        renderer.completeTasks()
-        renderer.addRule(rule1)
-        renderer.addRule(rule2)
-
-        then:
-        writer.value == '''Default tasks: task1, task2
-
-Group tasks
------------
-:task1 - task1Description [:task11, :task12]
-    :task2
-:task3 [:task1]
-
-Rules
------
-rule1Description
-rule2Description
-'''
-    }
-
-    def writesTasksForSingleGroup() {
-        TaskDetails task = taskDetails(':task1')
-
-        when:
-        renderer.addDefaultTasks([])
-        renderer.startTaskGroup('group')
-        renderer.addTask(task)
-        renderer.completeTasks()
-
-        then:
-        writer.value == '''Group tasks
------------
-:task1
-'''
-    }
-
-    def writesTasksForMultipleGroups() {
-        TaskDetails task = taskDetails(':task1')
-        TaskDetails task2 = taskDetails(':task2')
-
-        when:
-        renderer.addDefaultTasks([])
-        renderer.startTaskGroup('group')
-        renderer.addTask(task)
-        renderer.startTaskGroup('other')
-        renderer.addTask(task2)
-        renderer.completeTasks()
-
-        then:
-        writer.value == '''Group tasks
------------
-:task1
-
-Other tasks
------------
-:task2
-'''
-    }
-
-    def writesTasksForDefaultGroup() {
-        TaskDetails task = taskDetails(':task1')
-
-        when:
-        renderer.addDefaultTasks([])
-        renderer.startTaskGroup('')
-        renderer.addTask(task)
-        renderer.completeTasks()
-
-        then:
-        writer.value == '''Tasks
------
-:task1
-'''
-    }
-
-    def writesProjectWithNoTasksAndNoRules() {
-        when:
-        renderer.completeTasks()
-
-        then:
-        writer.value == '''No tasks
-'''
-    }
-
-    def writesProjectWithRulesAndNoTasks() {
-        String ruleDescription = "someDescription"
-
-        when:
-        renderer.completeTasks()
-        renderer.addRule([getDescription: {ruleDescription}] as Rule)
-
-        then:
-        writer.value == '''No tasks
-
-Rules
------
-someDescription
-'''
-    }
-}
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
deleted file mode 100644
index c81142b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
+++ /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.tasks.diagnostics.internal;
-
-
-import org.gradle.api.Project
-import org.gradle.logging.internal.StreamingStyledTextOutput
-import org.gradle.logging.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/util/PatternSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
index 2579129..ddba386 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
@@ -20,14 +20,11 @@ import org.gradle.api.file.FileTreeElement
 import org.gradle.api.file.RelativePath
 import org.gradle.api.specs.Spec
 import org.junit.Test
-import static org.gradle.util.Matchers.*
+
+import static org.gradle.util.Matchers.strictlyEqual
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.junit.After
 
-/**
-* @author Hans Dockter
-*/
 class PatternSetTest extends AbstractTestForPatternSet {
     PatternSet patternSet = new PatternSet()
 
@@ -35,10 +32,6 @@ class PatternSetTest extends AbstractTestForPatternSet {
         patternSet
     }
 
-    @After public void resetExcludes() {
-        PatternSet.resetGlobalExcludes()
-    }
-
     @Test public void testConstructionFromMap() {
         Map map = [includes: [TEST_PATTERN_1], excludes: [TEST_PATTERN_2]]
         PatternFilterable patternSet = new PatternSet(map)
@@ -221,19 +214,12 @@ class PatternSetTest extends AbstractTestForPatternSet {
         assertFalse(spec.isSatisfiedBy(element(true, '132')))
     }
 
-    @Test public void addsGlobalExcludesToExcludePatterns() {
+    @Test public void globalExcludes() {
         Spec<FileTreeElement> spec = patternSet.asSpec
 
         assertFalse(spec.isSatisfiedBy(element(false, '.svn')))
         assertFalse(spec.isSatisfiedBy(element(true, '.svn', 'abc')))
         assertFalse(spec.isSatisfiedBy(element(false, 'a', 'b', '.svn')))
         assertFalse(spec.isSatisfiedBy(element(true, 'a', 'b', '.svn', 'c')))
-
-        PatternSet.globalExcludes = ['*a*']
-
-        spec = patternSet.asSpec
-
-        assertTrue(spec.isSatisfiedBy(element(false, '.svn')))
-        assertFalse(spec.isSatisfiedBy(element(true, 'abc')))
     }
 }
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
index 06367b0..ae3e2f1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
@@ -15,12 +15,13 @@
  */
 package org.gradle.cache.internal
 
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
 import org.gradle.internal.Factory
 import org.gradle.messaging.serialize.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 {
@@ -37,94 +38,109 @@ class DefaultCacheAccessTest extends Specification {
         }
     }
 
-    def "executes cache action and returns result"() {
-        Factory<String> action = Mock()
+    def "acquires lock on open and releases on close when initial lock mode is not none"() {
+        when:
+        manager.open(Shared)
 
-        given:
-        manager.open(None)
+        then:
+        1 * lockManager.lock(lockFile, Shared, "<display-name>") >> lock
+        0 * _._
 
         when:
-        def result = manager.useCache("some operation", action)
+        manager.close()
 
         then:
-        result == 'result'
-
-        and:
-        1 * action.create() >> 'result'
+        1 * lock.close()
         0 * _._
     }
 
-    def "can create cache instance outside of cache action"() {
-        given:
+    def "does not acquires lock on open when initial lock mode is none"() {
+        when:
         manager.open(None)
 
+        then:
+        0 * _._
+
         when:
-        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+        manager.close()
 
         then:
-        cache instanceof MultiProcessSafePersistentIndexedCache
         0 * _._
     }
 
-    def "can create cache instance inside of cache action"() {
-        def cache
+    def "acquires lock at the start of the cache action and releases lock at the end of the cache action when initial lock mode is none"() {
+        Factory<String> action = Mock()
 
         given:
         manager.open(None)
 
         when:
-        manager.useCache("init", {
-            cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
-        } as Factory)
+        manager.useCache("some operation", action)
 
         then:
-        cache instanceof MultiProcessSafePersistentIndexedCache
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+
+        and:
+        1 * action.create()
+
+        and:
+        1 * lock.close()
         0 * _._
     }
 
-    def "acquires lock on open and releases on close when initial lock mode is not none"() {
-        when:
-        manager.open(Shared)
+    def "does not acquire lock at start of cache action when initial lock mode is exclusive"() {
+        Factory<String> action = Mock()
 
-        then:
-        1 * lockManager.lock(lockFile, Shared, "<display-name>") >> lock
-        0 * _._
+        given:
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
+        manager.open(Exclusive)
+        def cache = manager.newCache(targetFile, String, Integer)
 
         when:
-        manager.close()
+        manager.useCache("some operation", action)
 
         then:
-        1 * lock.close()
+        1 * action.create() >> {
+            canAccess cache
+        }
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
+
+        and:
         0 * _._
     }
 
-    def "does not acquires lock on open when initial lock mode is none"() {
-        when:
+    def "can create cache instance outside of cache action"() {
+        given:
         manager.open(None)
 
-        then:
-        0 * _._
-
         when:
-        manager.close()
+        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
 
         then:
+        cache instanceof MultiProcessSafePersistentIndexedCache
         0 * _._
     }
 
-    def "does not acquire lock when no caches used during cache action"() {
+    def "can create cache instance inside of cache action"() {
+        def cache
+
         given:
         manager.open(None)
-        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
 
         when:
-        manager.useCache("some operation", {} as Factory)
+        manager.useCache("init", {
+            cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+        } as Factory)
 
         then:
-        0 * _._
+        cache instanceof MultiProcessSafePersistentIndexedCache
+
+        and:
+        1 * lockManager.lock(lockFile, Exclusive, _, _) >> lock
     }
 
-    def "acquires lock when a cache is used and releases lock at the end of the cache action when initial lock mode is none"() {
+    def "can use cache instance during cache action"() {
         Factory<String> action = Mock()
 
         given:
@@ -136,7 +152,7 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
@@ -147,12 +163,12 @@ class DefaultCacheAccessTest extends Specification {
         0 * _._
     }
 
-    def "does not acquire lock at start of cache action when initial lock mode is exclusive"() {
+    def "releases lock before long running operation and reacquires after"() {
         Factory<String> action = Mock()
+        Factory<String> longRunningAction = Mock()
 
         given:
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
-        manager.open(Exclusive)
+        manager.open(None)
         def cache = manager.newCache(targetFile, String, Integer)
 
         when:
@@ -160,18 +176,25 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
+            manager.longRunningOperation("nested", longRunningAction)
+            canAccess cache
         }
+        1 * longRunningAction.create()
+        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
         _ * lock.writeFile(_)
-
-        and:
+        2 * lock.close()
         0 * _._
     }
 
-    def "releases lock before long running operation and reacquires after"() {
+    def "releases lock before nested long running operation and reacquires after"() {
         Factory<String> action = Mock()
-        Factory<String> longRunningAction = Mock()
+        Factory<String> lockInsideLongRunningOperation = Mock()
+        Factory<String> nestedLongRunningAction = Mock()
+        Factory<String> deeplyNestedAction = Mock()
+
+        FileLock anotherLock = Mock()
 
         given:
         manager.open(None)
@@ -182,15 +205,33 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
-            manager.longRunningOperation("nested", longRunningAction)
-            cache.get("key")
+            canAccess cache
+            manager.longRunningOperation("nested", lockInsideLongRunningOperation)
+            canAccess cache
         }
-        1 * longRunningAction.create()
+        1 * lockInsideLongRunningOperation.create() >> {
+            cannotAccess cache
+            manager.useCache("nested operation", nestedLongRunningAction)
+            cannotAccess cache
+        }
+        1 * nestedLongRunningAction.create() >> {
+            canAccess cache
+            manager.longRunningOperation("nested-2", deeplyNestedAction)
+            canAccess cache
+        }
+        1 * deeplyNestedAction.create() >> {
+            cannotAccess cache
+        }
+
         2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
         _ * lock.writeFile(_)
         2 * lock.close()
+
+        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested operation") >> anotherLock
+        _ * anotherLock.readFile(_)
+        _ * anotherLock.writeFile(_)
+        2 * anotherLock.close()
         0 * _._
     }
 
@@ -218,17 +259,13 @@ class DefaultCacheAccessTest extends Specification {
         manager.useCache("some operation", action)
 
         then:
-        IllegalStateException e = thrown()
-        e.message == 'The <display-name> has not been locked.'
-
-        and:
+        _ * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         1 * action.create() >> {
             manager.longRunningOperation("nested", longRunningAction)
         }
         1 * longRunningAction.create() >> {
-            cache.get("key")
+            cannotAccess cache
         }
-        0 * _._
     }
 
     def "can execute cache action from within long running operation"() {
@@ -244,18 +281,35 @@ class DefaultCacheAccessTest extends Specification {
         manager.useCache("some operation", action)
 
         then:
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+
+        and:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
             manager.longRunningOperation("nested", longRunningAction)
+            canAccess cache
         }
+
+        and:
+        1 * lock.close()
+
+        and:
         1 * longRunningAction.create() >> {
+            cannotAccess cache
             manager.useCache("nested 2", nestedAction)
+            cannotAccess cache
         }
+
+        and:
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested 2") >> lock
+
+        and:
         1 * nestedAction.create() >> {
-            cache.get("key")
+            canAccess cache
         }
+
+        and:
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested 2") >> lock
         _ * lock.readFile(_)
         _ * lock.writeFile(_)
         2 * lock.close()
@@ -276,17 +330,22 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
             manager.longRunningOperation("nested", longRunningAction)
+            canAccess cache
         }
         1 * longRunningAction.create() >> {
+            cannotAccess cache
             manager.longRunningOperation("nested 2", nestedAction)
+            cannotAccess cache
         }
-        1 * nestedAction.create()
-        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        1 * nestedAction.create() >> {
+            cannotAccess cache
+        }
+        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
         _ * lock.writeFile(_)
-        1 * lock.close()
+        2 * lock.close()
         0 * _._
     }
 
@@ -303,11 +362,12 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
             manager.useCache("nested", nestedAction)
+            canAccess cache
         }
         1 * nestedAction.create() >> {
-            cache.get("key")
+            canAccess cache
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
@@ -328,7 +388,7 @@ class DefaultCacheAccessTest extends Specification {
 
         then:
         1 * action.create() >> {
-            cache.get("key")
+            canAccess cache
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         _ * lock.readFile(_)
@@ -362,4 +422,21 @@ class DefaultCacheAccessTest extends Specification {
         0 * _._
     }
 
+    def canAccess(def cache) {
+        try {
+            cache.get("key")
+        } catch (IllegalStateException e) {
+            assert false: "Should be able to access cache here"
+        }
+    }
+
+    def cannotAccess(def cache) {
+        try {
+            cache.get("key")
+            assert false: "Should not be able to access cache here"
+        } catch (IllegalStateException e) {
+            assert e.message == 'The <display-name> has not been locked.'
+        }
+    }
+
 }
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
index 2d6948a..8d7fada 100755
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
@@ -92,7 +92,7 @@ class DefaultCacheFactoryTest extends Specification {
             then:
             cache instanceof DelegateOnDemandPersistentDirectoryCache
             cache.baseDir == tmpDir.dir
-            cache.toString().startsWith "Delegate On Demand Cache for <display>"
+            cache.toString().startsWith "On Demand Cache for <display>"
 
             when:
             factory.close()
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
index ba26dce..41c06d6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
@@ -17,8 +17,6 @@ package org.gradle.cache.internal
 
 import org.gradle.cache.internal.FileLockManager.LockMode
 import org.gradle.internal.Factory
-import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
@@ -106,7 +104,7 @@ class OnDemandFileAccessTest extends Specification {
     }
 
     FileLockManager createManager() {
-        new DefaultFileLockManager(new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment)))
+        return DefaultFileLockManagerTestHelper.createDefaultFileLockManager()
     }
 
     FileAccess access(File file, FileLockManager manager = createManager()) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
index f6e694d..47638c0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
@@ -15,12 +15,7 @@
  */
 package org.gradle.configuration
 
-import org.gradle.api.DefaultTask
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.tasks.diagnostics.DependencyReportTask
-import org.gradle.api.tasks.diagnostics.ProjectReportTask
-import org.gradle.api.tasks.diagnostics.PropertyReportTask
-import org.gradle.api.tasks.diagnostics.TaskReportTask
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
@@ -28,15 +23,11 @@ class ImplicitTasksConfigurerTest extends Specification {
     private final ImplicitTasksConfigurer configurer = new ImplicitTasksConfigurer()
     private final ProjectInternal project = HelperUtil.createRootProject()
 
-    def addsImplicitTasksToProject() {
+    def "applies help tasks"() {
         when:
         configurer.execute(project)
 
         then:
-        project.implicitTasks['help'] instanceof DefaultTask
-        project.implicitTasks['projects'] instanceof ProjectReportTask
-        project.implicitTasks['tasks'] instanceof TaskReportTask
-        project.implicitTasks['dependencies'] instanceof DependencyReportTask
-        project.implicitTasks['properties'] instanceof PropertyReportTask
+        project.plugins.hasPlugin('help-tasks')
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
deleted file mode 100644
index aced70f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
+++ /dev/null
@@ -1,579 +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 org.gradle.api.CircularReferenceException;
-import org.gradle.api.Task;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.execution.TaskExecutionListener;
-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.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;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-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.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultTaskGraphExecuterTest {
-
-    JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
-    DefaultTaskGraphExecuter taskExecuter;
-    ProjectInternal root;
-    List<Task> executedTasks = new ArrayList<Task>();
-
-    @Before
-    public void setUp() {
-        root = createRootProject();
-        context.checking(new Expectations(){{
-            one(listenerManager).createAnonymousBroadcaster(TaskExecutionGraphListener.class);
-            will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
-            one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
-            will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
-        }});
-        taskExecuter = new DefaultTaskGraphExecuter(listenerManager);
-    }
-
-    @Test
-    public void testExecutesTasksInDependencyOrder() {
-        Task a = task("a");
-        Task b = task("b", a);
-        Task c = task("c", b, a);
-        Task d = task("d", c);
-
-        taskExecuter.execute(toList(d));
-
-        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
-    }
-
-    @Test
-    public void testExecutesDependenciesInNameOrder() {
-        Task a = task("a");
-        Task b = task("b");
-        Task c = task("c");
-        Task d = task("d", b, a, c);
-
-        taskExecuter.execute(toList(d));
-
-        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
-    }
-
-    @Test
-    public void testExecutesTasksInASingleBatchInNameOrder() {
-        Task a = task("a");
-        Task b = task("b");
-        Task c = task("c");
-
-        taskExecuter.execute(toList(b, c, a));
-
-        assertThat(executedTasks, equalTo(toList(a, b, c)));
-    }
-
-    @Test
-    public void testExecutesBatchesInOrderAdded() {
-        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));
-        taskExecuter.execute();
-
-        assertThat(executedTasks, equalTo(toList(b, c, a, d)));
-    }
-
-    @Test
-    public void testExecutesSharedDependenciesOfBatchesOnceOnly() {
-        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));
-        taskExecuter.execute();
-
-        assertThat(executedTasks, equalTo(toList(a, b, c, d, e)));
-    }
-
-    @Test
-    public void testAddTasksAddsDependencies() {
-        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"));
-        assertTrue(taskExecuter.hasTask(a));
-        assertTrue(taskExecuter.hasTask(":b"));
-        assertTrue(taskExecuter.hasTask(b));
-        assertTrue(taskExecuter.hasTask(":c"));
-        assertTrue(taskExecuter.hasTask(c));
-        assertTrue(taskExecuter.hasTask(":d"));
-        assertTrue(taskExecuter.hasTask(d));
-        assertThat(taskExecuter.getAllTasks(), equalTo(toList(a, b, c, d)));
-    }
-
-    @Test
-    public void testGetAllTasksReturnsTasksInExecutionOrder() {
-        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)));
-    }
-
-    @Test
-    public void testCannotUseGetterMethodsWhenGraphHasNotBeenCalculated() {
-        try {
-            taskExecuter.hasTask(":a");
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo(
-                    "Task information is not available, as this task execution graph has not been populated."));
-        }
-
-        try {
-            taskExecuter.hasTask(task("a"));
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo(
-                    "Task information is not available, as this task execution graph has not been populated."));
-        }
-
-        try {
-            taskExecuter.getAllTasks();
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo(
-                    "Task information is not available, as this task execution graph has not been populated."));
-        }
-    }
-
-    @Test
-    public void testDiscardsTasksAfterExecute() {
-        Task a = task("a");
-        Task b = task("b", a);
-
-        taskExecuter.addTasks(toList(b));
-        taskExecuter.execute();
-
-        assertFalse(taskExecuter.hasTask(":a"));
-        assertFalse(taskExecuter.hasTask(a));
-        assertTrue(taskExecuter.getAllTasks().isEmpty());
-    }
-
-    @Test
-    public void testCanExecuteMultipleTimes() {
-        Task a = task("a");
-        Task b = task("b", a);
-        Task c = task("c");
-
-        taskExecuter.addTasks(toList(b));
-        taskExecuter.execute();
-        assertThat(executedTasks, equalTo(toList(a, b)));
-
-        executedTasks.clear();
-
-        taskExecuter.addTasks(toList(c));
-
-        assertThat(taskExecuter.getAllTasks(), equalTo(toList(c)));
-
-        taskExecuter.execute();
-
-        assertThat(executedTasks, equalTo(toList(c)));
-    }
-
-    @Test
-    public void testCannotAddTaskWithCircularReference() {
-        Task a = createTask("a");
-        Task b = task("b", a);
-        Task c = task("c", b);
-        dependsOn(a, c);
-
-        try {
-            taskExecuter.addTasks(toList(c));
-            fail();
-        } catch (CircularReferenceException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void testNotifiesGraphListenerBeforeExecute() {
-        final TaskExecutionGraphListener listener = context.mock(TaskExecutionGraphListener.class);
-        Task a = task("a");
-
-        taskExecuter.addTaskExecutionGraphListener(listener);
-        taskExecuter.addTasks(toList(a));
-
-        context.checking(new Expectations() {{
-            one(listener).graphPopulated(taskExecuter);
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    public void testExecutesWhenReadyClosureBeforeExecute() {
-        final TestClosure runnable = context.mock(TestClosure.class);
-        Task a = task("a");
-
-        taskExecuter.whenReady(toClosure(runnable));
-
-        taskExecuter.addTasks(toList(a));
-
-        context.checking(new Expectations() {{
-            one(runnable).call(taskExecuter);
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    public void testNotifiesTaskListenerAsTasksAreExecuted() {
-        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
-        final Task a = task("a");
-        final Task b = task("b");
-
-        taskExecuter.addTaskExecutionListener(listener);
-        taskExecuter.addTasks(toList(a, b));
-
-        context.checking(new Expectations() {{
-            one(listener).beforeExecute(a);
-            one(listener).afterExecute(with(equalTo(a)), with(notNullValue(TaskState.class)));
-            one(listener).beforeExecute(b);
-            one(listener).afterExecute(with(equalTo(b)), with(notNullValue(TaskState.class)));
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    public void testNotifiesTaskListenerWhenTaskFails() {
-        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
-        final RuntimeException failure = new RuntimeException();
-        final Task a = brokenTask("a", failure);
-
-        taskExecuter.addTaskExecutionListener(listener);
-        taskExecuter.addTasks(toList(a));
-
-        context.checking(new Expectations() {{
-            one(listener).beforeExecute(a);
-            one(listener).afterExecute(with(sameInstance(a)), with(notNullValue(TaskState.class)));
-        }});
-
-        try {
-            taskExecuter.execute();
-            fail();
-        } 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 = task("a");
-        final Task b = task("b");
-
-        taskExecuter.beforeTask(toClosure(runnable));
-
-        taskExecuter.addTasks(toList(a, b));
-
-        context.checking(new Expectations() {{
-            one(runnable).call(a);
-            one(runnable).call(b);
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    public void testNotifiesAfterTaskClosureAsTasksAreExecuted() {
-        final TestClosure runnable = context.mock(TestClosure.class);
-        final Task a = task("a");
-        final Task b = task("b");
-
-        taskExecuter.afterTask(toClosure(runnable));
-
-        taskExecuter.addTasks(toList(a, b));
-
-        context.checking(new Expectations() {{
-            one(runnable).call(a);
-            one(runnable).call(b);
-        }});
-
-        taskExecuter.execute();
-    }
-
-    @Test
-    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;
-            }
-        };
-
-        taskExecuter.useFilter(spec);
-        taskExecuter.addTasks(toList(a, b));
-        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b)));
-
-        taskExecuter.execute();
-        
-        assertThat(executedTasks, equalTo(toList(b)));
-    }
-
-    @Test
-    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;
-            }
-        };
-
-        taskExecuter.useFilter(spec);
-        taskExecuter.addTasks(toList(c));
-        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b, c)));
-        
-        taskExecuter.execute();
-                
-        assertThat(executedTasks, equalTo(toList(b, c)));
-    }
-
-    @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/ExcludedTaskFilteringBuildConfigurationActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
index 55b72d3..9ada63e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
@@ -19,18 +19,21 @@ import org.gradle.StartParameter
 import org.gradle.api.internal.GradleInternal
 import spock.lang.Specification
 
+import static java.util.Collections.emptySet
+
 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)
+    final action = Spy(ExcludedTaskFilteringBuildConfigurationAction)
 
     def setup() {
         _ * context.gradle >> gradle
         _ * gradle.startParameter >> startParameter
         _ * gradle.taskGraph >> taskGraph
+        _ * action.createSelector(gradle) >> selector
     }
 
     def "calls proceed when there are no excluded tasks defined"() {
@@ -52,8 +55,7 @@ class ExcludedTaskFilteringBuildConfigurationActionTest extends Specification {
         action.configure(context)
 
         then:
-        1 * selector.selectTasks(gradle, 'a')
-        _ * selector.tasks >> []
+        1 * selector.getSelection('a') >> new TaskSelector.TaskSelection('a', emptySet())
         1 * taskGraph.useFilter(!null)
         1 * context.proceed()
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy
index 8bdd330..d4d099c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy
@@ -15,13 +15,11 @@
  */
 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
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.tasks.TaskState
+import spock.lang.Specification
 
 class SelectedTaskExecutionActionTest extends Specification {
     final SelectedTaskExecutionAction action = new SelectedTaskExecutionAction()
@@ -59,7 +57,7 @@ class SelectedTaskExecutionActionTest extends Specification {
         1 * executer.execute()
     }
 
-    def "rethrows single failure after build when continue specified"() {
+    def "adds failure handler that does not abort execution when continue specified"() {
         TaskFailureHandler handler
         RuntimeException failure = new RuntimeException()
 
@@ -70,38 +68,16 @@ class SelectedTaskExecutionActionTest extends Specification {
         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
+        _ * state.rethrowFailure() >> { throw failure }
         return task
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
new file mode 100644
index 0000000..ee906c9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskConfigurerSpec.groovy
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.internal.tasks.CommandLineOption
+import org.gradle.api.tasks.TaskAction
+import org.gradle.execution.TaskSelector
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/8/12
+ */
+class CommandLineTaskConfigurerSpec extends Specification {
+
+    Project project = new ProjectBuilder().build()
+    CommandLineTaskConfigurer configurer = new CommandLineTaskConfigurer()
+
+    TaskSelector selector = Mock()
+    SomeTask task = project.task('someTask', type: SomeTask)
+    SomeTask task2 = project.task('someTask2', type: SomeTask)
+    SomeOtherTask otherTask = project.task('otherTask', type: SomeOtherTask)
+    DefaultTask defaultTask = project.task('defaultTask')
+
+    def "does not configure if option doesn't match"() {
+        when:
+        configurer.configureTasks([task, task2], ['foo'])
+        then:
+        task.content == 'default content'
+        task2.content == 'default content'
+        !task.someFlag
+        !task2.someFlag
+        !task2.someFlag2
+        !task2.someFlag2
+    }
+
+    def "does not attempt configure if no options"() {
+        configurer = Spy(CommandLineTaskConfigurer)
+
+        when:
+        def out = configurer.configureTasks([task, task2], ['foo'])
+
+        then:
+        out == ['foo']
+        0 * configurer.configureTasksNow(_, _)
+    }
+
+    def "configures string option on all tasks"() {
+        when:
+        configurer.configureTasks([task, task2], ['--content', 'Hey!', 'foo'])
+        then:
+        task.content == 'Hey!'
+        task2.content == 'Hey!'
+    }
+
+    def "configures boolean option"() {
+        when:
+        configurer.configureTasks([task], ['--someFlag'])
+        then:
+        task.someFlag
+    }
+
+    def "configures options on all types that can accommodate the setting"() {
+        when:
+        configurer.configureTasks([task, otherTask], ['--someFlag'])
+        then:
+        task.someFlag
+        otherTask.someFlag
+    }
+
+    def "fails if some of the types cannot accommodate the setting"() {
+        when:
+        configurer.configureTasks([task, defaultTask], ['--someFlag'])
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains('someFlag')
+    }
+
+    def "fails if one of the options cannot be applied to one of the tasks"() {
+        when:
+        configurer.configureTasks([task, otherTask], input)
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains('someFlag2')
+
+        where:
+        input << [['--someFlag', '--someFlag2'], ['--someFlag2', '--someFlag']]
+    }
+
+    def "configures the Boolean option"() {
+        when:
+        configurer.configureTasks([task], ['--someFlag2'])
+        then:
+        task.someFlag2
+    }
+
+    def "configures mixed options"() {
+        when:
+        configurer.configureTasks([task, task2], ['--someFlag', '--content', 'Hey!'])
+        then:
+        task.someFlag
+        task2.someFlag
+        task.content == 'Hey!'
+        task2.content == 'Hey!'
+    }
+
+    def "configures options and returns unused arguments"() {
+        def args = ['--someFlag', '--content', 'Hey!', 'foo', '--baz', '--someFlag']
+        when:
+        def out = configurer.configureTasks([task, task2], args)
+        then:
+        out == ['foo', '--baz', '--someFlag']
+    }
+
+    def "fails on unknown option"() {
+        def args = ['--xxx']
+        when:
+        configurer.configureTasks([task, task2], args)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains('xxx')
+    }
+
+    public static class SomeTask extends DefaultTask {
+        String content = 'default content'
+        @CommandLineOption(options="content", description="Some content.") public void setContent(String content) {
+            this.content = content
+        }
+
+        boolean someFlag = false
+        @CommandLineOption(options="someFlag", description="Some flag.") public void setSomeFlag(boolean someFlag) {
+            this.someFlag = someFlag
+        }
+
+        Boolean someFlag2 = false
+        @CommandLineOption(options="someFlag2", description="Some 2nd flag.") public void setSomeFlag2(Boolean someFlag2) {
+            this.someFlag2 = someFlag2
+        }
+
+        @CommandLineOption(options="notUsed", description="Not used.") public void setNotUsed(boolean notUsed) {
+            throw new RuntimeException("Not used");
+        }
+
+        @TaskAction public void dummy() {}
+    }
+
+    public static class SomeOtherTask extends DefaultTask {
+        boolean someFlag = false
+        String stuff
+        @CommandLineOption(options="someFlag", description="Some flag.") public void setSomeFlag(boolean someFlag) {
+            this.someFlag = someFlag
+        }
+        @CommandLineOption(options="stuff", description="Some stuff.") public void setStuff(String stuff) {
+            this.stuff = stuff;
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
new file mode 100644
index 0000000..b7662da
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/commandline/CommandLineTaskParserSpec.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.commandline
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.tasks.TaskAction
+import org.gradle.execution.TaskSelector
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+import static com.google.common.collect.Sets.newHashSet
+import static java.util.Collections.emptyList
+
+/**
+ * by Szczepan Faber, created at: 10/8/12
+ */
+class CommandLineTaskParserSpec extends Specification {
+
+    Project project = new ProjectBuilder().build()
+    CommandLineTaskParser parser = new CommandLineTaskParser()
+    TaskSelector selector = Mock()
+    SomeTask task = project.task('someTask', type: SomeTask)
+    SomeTask task2 = project.task('someTask2', type: SomeTask)
+    SomeTask task3 = project.task('someTask3', type: SomeTask)
+
+    def setup() {
+        parser.taskConfigurer = Mock(CommandLineTaskConfigurer)
+        parser.taskConfigurer.configureTasks(_, _) >> { args -> args[1] }
+    }
+
+    def "deals with empty input"() {
+        expect:
+        parser.parseTasks(emptyList(), selector).empty
+    }
+
+    def "parses a single task"() {
+        given:
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task] as Set)
+
+        when:
+        def out = parser.parseTasks(['foo'], selector)
+
+        then:
+        out.size() == 1
+        out.get('foo task') == [task] as Set
+    }
+
+    def "parses single task with multiple matches"() {
+        given:
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task, task2] as Set)
+
+        when:
+        def out = parser.parseTasks(['foo'], selector)
+
+        then:
+        out.size() == 2
+        out.get('foo task') == [task, task2] as Set
+    }
+
+    def "reports incorrect commandline task configuration"() {
+        when:
+        parser.parseTasks(['tasks', '-l', '-l'], selector)
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains('-l')
+    }
+
+    def "parses multiple matching tasks"() {
+        given:
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task, task2] as Set)
+        selector.getSelection('bar') >> new TaskSelector.TaskSelection('bar task', [task3] as Set)
+
+        when:
+        def out = parser.parseTasks(['foo', 'bar'], selector)
+
+        then:
+        out.size() == 3
+        out.get('foo task') == [task, task2] as Set
+        out.get('bar task') == [task3] as Set
+    }
+
+    def "configures tasks if configuration options specified"() {
+        given:
+        selector.getSelection('foo') >> new TaskSelector.TaskSelection('foo task', [task, task2] as Set)
+        selector.getSelection('bar') >> new TaskSelector.TaskSelection('bar task', [task3] as Set)
+        selector.getSelection('lastTask') >> new TaskSelector.TaskSelection('last task', [task3] as Set)
+
+        when:
+        def out = parser.parseTasks(['foo', '--all', 'bar', '--include', 'stuff', 'lastTask'], selector)
+
+        then:
+        out.size() == 4
+        1 * parser.taskConfigurer.configureTasks(newHashSet(task, task2), ['--all', 'bar', '--include', 'stuff', 'lastTask']) >> ['bar', '--include', 'stuff', 'lastTask']
+        1 * parser.taskConfigurer.configureTasks(newHashSet(task3), ['--include', 'stuff', 'lastTask']) >> ['lastTask']
+        1 * parser.taskConfigurer.configureTasks(newHashSet(task3), []) >> []
+        0 * parser.taskConfigurer._
+    }
+
+    public static class SomeTask extends DefaultTask {
+        @TaskAction public void dummy() {}
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
new file mode 100644
index 0000000..f35b19c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taskgraph;
+
+
+import org.gradle.api.CircularReferenceException
+import org.gradle.api.Task
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.tasks.TaskState
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.execution.TaskFailureHandler
+import org.gradle.internal.Factory
+import org.gradle.messaging.serialize.Serializer
+import org.hamcrest.Description
+import org.jmock.api.Invocation
+import spock.lang.Specification
+
+import static org.gradle.util.HelperUtil.createRootProject
+import static org.gradle.util.WrapUtil.toList
+import static org.gradle.util.WrapUtil.toSet
+
+public class DefaultTaskExecutionPlanTest extends Specification {
+
+    DefaultTaskExecutionPlan executionPlan
+    ProjectInternal root;
+    Spec<TaskInfo> anyTask = Specs.satisfyAll();
+
+    def setup() {
+        root = createRootProject();
+        executionPlan = new DefaultTaskExecutionPlan()
+    }
+
+    def "returns tasks in dependency order"() {
+        given:
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c", b, a);
+        Task d = task("d", c);
+
+        when:
+        executionPlan.addToTaskGraph(toList(d))
+
+        then:
+        executedTasks == [a, b, c, d]
+    }
+
+    def "returns task dependencies in name order"() {
+        given:
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+        Task d = task("d", b, a, c);
+
+        when:
+        executionPlan.addToTaskGraph(toList(d));
+
+        then:
+        executedTasks == [a, b, c, d]
+    }
+
+    def "returns a single batch of tasks in name order"() {
+        given:
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+
+        when:
+        executionPlan.addToTaskGraph(toList(b, c, a));
+
+        then:
+        executedTasks == [a, b, c]
+    }
+
+    def "returns separately added tasks in order added"() {
+        given:
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+        Task d = task("d");
+
+        when:
+        executionPlan.addToTaskGraph(toList(c, b));
+        executionPlan.addToTaskGraph(toList(d, a));
+
+        then:
+        executedTasks == [b, c, a, d];
+    }
+
+    def "common tasks in separate batches are returned only once"() {
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c", a, b);
+        Task d = task("d");
+        Task e = task("e", b, d);
+
+        when:
+        executionPlan.addToTaskGraph(toList(c));
+        executionPlan.addToTaskGraph(toList(e));
+
+        then:
+        executedTasks == [a, b, c, d, e];
+    }
+
+    def "all dependencies added when adding tasks"() {
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c", b, a);
+        Task d = task("d", c);
+
+        when:
+        executionPlan.addToTaskGraph(toList(d));
+
+        then:
+        executionPlan.getTasks() == [a, b, c, d];
+        executedTasks == [a, b, c, d]
+    }
+
+    def "getAllTasks returns tasks in execution order"() {
+        Task d = task("d");
+        Task c = task("c");
+        Task b = task("b", d, c);
+        Task a = task("a", b);
+
+        when:
+        executionPlan.addToTaskGraph(toList(a));
+
+        then:
+        executionPlan.getTasks() == [c, d, b, a]
+        executedTasks == [c, d, b, a]
+    }
+
+    def "cannot add task with circular reference"() {
+        Task a = createTask("a");
+        Task b = task("b", a);
+        Task c = task("c", b);
+        dependsOn(a, c);
+
+        when:
+        executionPlan.addToTaskGraph([c])
+
+        then:
+        thrown CircularReferenceException
+    }
+
+    def "stops returning tasks on task execution failure"() {
+        RuntimeException failure = new RuntimeException("failure");
+        Task a = task("a");
+        Task b = task("b");
+        executionPlan.addToTaskGraph([a, b])
+
+        when:
+        def taskInfoA = executionPlan.getTaskToExecute(anyTask)
+        taskInfoA.executionFailure = failure
+        executionPlan.taskComplete(taskInfoA)
+
+        then:
+        executedTasks == []
+
+        when:
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "stops returning tasks on first task failure when no failure handler provided"() {
+        RuntimeException failure = new RuntimeException("failure");
+        Task a = brokenTask("a", failure);
+        Task b = task("b");
+
+        when:
+        executionPlan.addToTaskGraph([a, b])
+
+        then:
+        executedTasks == [a]
+
+        when:
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "stops execution on task failure when failure handler indicates that execution should stop"() {
+        RuntimeException failure = new RuntimeException("failure");
+        Task a = brokenTask("a", failure);
+        Task b = task("b");
+
+        executionPlan.addToTaskGraph([a, b])
+
+        TaskFailureHandler handler = Mock()
+        RuntimeException wrappedFailure = new RuntimeException("wrapped");
+        handler.onTaskFailure(a) >> {
+            throw wrappedFailure
+        }
+
+        when:
+        executionPlan.useFailureHandler(handler);
+
+        then:
+        executedTasks == [a]
+
+        when:
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == wrappedFailure
+    }
+
+    def "continues to return tasks and rethrows failure on completion when failure handler indicates that execution should continue"() {
+        RuntimeException failure = new RuntimeException();
+        Task a = brokenTask("a", failure);
+        Task b = task("b");
+        executionPlan.addToTaskGraph([a, b])
+
+        TaskFailureHandler handler = Mock()
+        handler.onTaskFailure(a) >> {
+        }
+
+        when:
+        executionPlan.useFailureHandler(handler);
+
+        then:
+        executedTasks == [a, b]
+
+        when:
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "does not attempt to execute tasks whose dependencies failed to execute"() {
+        RuntimeException failure = new RuntimeException()
+        final Task a = brokenTask("a", failure)
+        final Task b = task("b", a)
+        final Task c = task("c")
+        executionPlan.addToTaskGraph([b, c])
+
+        TaskFailureHandler handler = Mock()
+        handler.onTaskFailure(a) >> {
+            // Ignore failure
+        }
+
+        when:
+        executionPlan.useFailureHandler(handler)
+
+        then:
+        executedTasks == [a, c]
+
+        when:
+        executionPlan.awaitCompletion()
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "clear removes all tasks"() {
+        given:
+        Task a = task("a");
+
+        when:
+        executionPlan.addToTaskGraph(toList(a));
+        executionPlan.clear()
+
+        then:
+        executionPlan.getTasks() == []
+        executedTasks == []
+    }
+
+    def getExecutedTasks() {
+        def tasks = []
+        def taskInfo
+        while ((taskInfo = executionPlan.getTaskToExecute(anyTask)) != null) {
+            tasks << taskInfo.task
+            executionPlan.taskComplete(taskInfo)
+        }
+        return tasks
+    }
+
+    def "can add additional tasks after execution and clear"() {
+        given:
+        Task a = task("a")
+        Task b = task("b")
+
+        when:
+        executionPlan.addToTaskGraph([a])
+
+        then:
+        executedTasks == [a]
+
+        when:
+        executionPlan.clear()
+        executionPlan.addToTaskGraph([b])
+
+        then:
+        executedTasks == [b]
+    }
+
+    def "does not execute filtered tasks"() {
+        given:
+        Task a = task("a", task("a-dep"))
+        Task b = task("b")
+
+        when:
+        executionPlan.useFilter({ it != a } as Spec<Task>);
+        executionPlan.addToTaskGraph([a, b])
+
+        then:
+        executionPlan.getTasks() == [b]
+        executedTasks == [b]
+    }
+
+    def "does not execute filtered dependencies"() {
+        given:
+        Task a = task("a", task("a-dep"))
+        Task b = task("b")
+        Task c = task("c", a, b)
+
+        when:
+
+        executionPlan.useFilter({ it != a } as Spec<Task>)
+        executionPlan.addToTaskGraph([c])
+
+        then:
+        executionPlan.tasks == [b, c]
+        executedTasks == [b, c]
+    }
+
+    def "will execute a task whose dependencies have been filtered"() {
+        given:
+        Task b = task("b")
+        Task c = task("c", b)
+
+        when:
+        executionPlan.useFilter({ it != b } as Spec<Task>)
+        executionPlan.addToTaskGraph([c]);
+
+        then:
+        executedTasks == [c]
+    }
+
+    private void dependsOn(TaskInternal task, final Task... dependsOnTasks) {
+        TaskDependency taskDependency = Mock()
+        task.getTaskDependencies() >> taskDependency
+        taskDependency.getDependencies(task) >> toSet(dependsOnTasks)
+    }
+    
+    private Task brokenTask(String name, final RuntimeException failure, final Task... dependsOnTasks) {
+        final TaskInternal task = createTask(name);
+        dependsOn(task, dependsOnTasks);
+
+        task.state.getFailure() >> failure
+        task.state.rethrowFailure() >> { throw failure }
+        return task;
+    }
+    
+    private TaskInternal task(final String name, final Task... dependsOnTasks) {
+        def task = createTask(name);
+        dependsOn(task, dependsOnTasks);
+        task.state.getFailure() >> null
+        return task;
+    }
+    
+    private TaskInternal createTask(final String name) {
+        TaskInternal task = Mock()
+        TaskState state = Mock()
+        task.getProject() >> root
+        task.name >> name
+        task.path >> ':' + name
+        task.state >> state
+        task.toString() >> "task $name"
+        task.compareTo(_ as TaskInternal) >> { TaskInternal taskInternal ->
+            return name.compareTo(taskInternal.getName());
+        }
+        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");
+        }
+    }
+
+    private static class DirectCacheAccess implements TaskArtifactStateCacheAccess {
+        public void useCache(String operationDisplayName, Runnable action) {
+            action.run();
+        }
+
+        public void longRunningOperation(String operationDisplayName, Runnable action) {
+            action.run();
+        }
+
+        public <K, V> PersistentIndexedCache createCache(String cacheName, Class<K> keyType, Class<V> valueType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+            throw new UnsupportedOperationException();
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Class<K> keyType, Class<V> valueType, Serializer<V> valueSerializer) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
new file mode 100644
index 0000000..7d9cd77
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskGraphExecuterTest.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taskgraph;
+
+import org.gradle.api.CircularReferenceException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
+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.TaskDependency;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.execution.TaskFailureHandler;
+import org.gradle.internal.Factory;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.messaging.serialize.Serializer;
+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;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultTaskGraphExecuterTest {
+
+    JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    DefaultTaskGraphExecuter taskExecuter;
+    ProjectInternal root;
+    List<Task> executedTasks = new ArrayList<Task>();
+
+    @Before
+    public void setUp() {
+        root = createRootProject();
+        context.checking(new Expectations(){{
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionGraphListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
+        }});
+        taskExecuter = new org.gradle.execution.taskgraph.DefaultTaskGraphExecuter(listenerManager, new DefaultTaskPlanExecutor());
+    }
+
+    @Test
+    public void testExecutesTasksInDependencyOrder() {
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c", b, a);
+        Task d = task("d", c);
+
+        taskExecuter.addTasks(toList(d));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testExecutesDependenciesInNameOrder() {
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+        Task d = task("d", b, a, c);
+
+        taskExecuter.addTasks(toList(d));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testExecutesTasksInASingleBatchInNameOrder() {
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+
+        taskExecuter.addTasks(toList(b, c, a));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b, c)));
+    }
+
+    @Test
+    public void testExecutesBatchesInOrderAdded() {
+        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));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(b, c, a, d)));
+    }
+
+    @Test
+    public void testExecutesSharedDependenciesOfBatchesOnceOnly() {
+        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));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d, e)));
+    }
+
+    @Test
+    public void testAddTasksAddsDependencies() {
+        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"));
+        assertTrue(taskExecuter.hasTask(a));
+        assertTrue(taskExecuter.hasTask(":b"));
+        assertTrue(taskExecuter.hasTask(b));
+        assertTrue(taskExecuter.hasTask(":c"));
+        assertTrue(taskExecuter.hasTask(c));
+        assertTrue(taskExecuter.hasTask(":d"));
+        assertTrue(taskExecuter.hasTask(d));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testGetAllTasksReturnsTasksInExecutionOrder() {
+        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)));
+    }
+
+    @Test
+    public void testCannotUseGetterMethodsWhenGraphHasNotBeenCalculated() {
+        try {
+            taskExecuter.hasTask(":a");
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+
+        try {
+            taskExecuter.hasTask(task("a"));
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+
+        try {
+            taskExecuter.getAllTasks();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+    }
+
+    @Test
+    public void testDiscardsTasksAfterExecute() {
+        Task a = task("a");
+        Task b = task("b", a);
+
+        taskExecuter.addTasks(toList(b));
+        taskExecuter.execute();
+
+        assertFalse(taskExecuter.hasTask(":a"));
+        assertFalse(taskExecuter.hasTask(a));
+        assertTrue(taskExecuter.getAllTasks().isEmpty());
+    }
+
+    @Test
+    public void testCanExecuteMultipleTimes() {
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c");
+
+        taskExecuter.addTasks(toList(b));
+        taskExecuter.execute();
+        assertThat(executedTasks, equalTo(toList(a, b)));
+
+        executedTasks.clear();
+
+        taskExecuter.addTasks(toList(c));
+
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(c)));
+
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(c)));
+    }
+
+    @Test
+    public void testCannotAddTaskWithCircularReference() {
+        Task a = createTask("a");
+        Task b = task("b", a);
+        Task c = task("c", b);
+        dependsOn(a, c);
+
+        try {
+            taskExecuter.addTasks(toList(c));
+            fail();
+        } catch (CircularReferenceException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testNotifiesGraphListenerBeforeExecute() {
+        final TaskExecutionGraphListener listener = context.mock(TaskExecutionGraphListener.class);
+        Task a = task("a");
+
+        taskExecuter.addTaskExecutionGraphListener(listener);
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(listener).graphPopulated(taskExecuter);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testExecutesWhenReadyClosureBeforeExecute() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        Task a = task("a");
+
+        taskExecuter.whenReady(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(taskExecuter);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesTaskListenerAsTasksAreExecuted() {
+        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
+        final Task a = task("a");
+        final Task b = task("b");
+
+        taskExecuter.addTaskExecutionListener(listener);
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(listener).beforeExecute(a);
+            one(listener).afterExecute(with(equalTo(a)), with(notNullValue(TaskState.class)));
+            one(listener).beforeExecute(b);
+            one(listener).afterExecute(with(equalTo(b)), with(notNullValue(TaskState.class)));
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesTaskListenerWhenTaskFails() {
+        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
+        final RuntimeException failure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+
+        taskExecuter.addTaskExecutionListener(listener);
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(listener).beforeExecute(a);
+            one(listener).afterExecute(with(sameInstance(a)), with(notNullValue(TaskState.class)));
+        }});
+
+        try {
+            taskExecuter.execute();
+            fail();
+        } 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 testNotifiesBeforeTaskClosureAsTasksAreExecuted() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        final Task a = task("a");
+        final Task b = task("b");
+
+        taskExecuter.beforeTask(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(a);
+            one(runnable).call(b);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesAfterTaskClosureAsTasksAreExecuted() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        final Task a = task("a");
+        final Task b = task("b");
+
+        taskExecuter.afterTask(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(a);
+            one(runnable).call(b);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    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;
+            }
+        };
+
+        taskExecuter.useFilter(spec);
+        taskExecuter.addTasks(toList(a, b));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b)));
+
+        taskExecuter.execute();
+        
+        assertThat(executedTasks, equalTo(toList(b)));
+    }
+
+    @Test
+    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;
+            }
+        };
+
+        taskExecuter.useFilter(spec);
+        taskExecuter.addTasks(toList(c));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b, c)));
+        
+        taskExecuter.execute();
+                
+        assertThat(executedTasks, equalTo(toList(b, c)));
+    }
+
+    @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);
+        }});
+        try {
+            taskExecuter.execute();
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+
+        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).getProject();
+            will(returnValue(root));
+            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");
+        }
+    }
+
+    private static class DirectCacheAccess implements TaskArtifactStateCacheAccess {
+        public void useCache(String operationDisplayName, Runnable action) {
+            action.run();
+        }
+
+        public void longRunningOperation(String operationDisplayName, Runnable action) {
+            action.run();
+        }
+
+        public <K, V> PersistentIndexedCache createCache(String cacheName, Class<K> keyType, Class<V> valueType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+            throw new UnsupportedOperationException();
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Class<K> keyType, Class<V> valueType, Serializer<V> valueSerializer) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactoryTest.groovy
new file mode 100644
index 0000000..36fa1be
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactoryTest.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.execution.taskgraph;
+
+
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess
+import spock.lang.Specification
+import org.gradle.api.internal.DocumentationRegistry
+
+public class TaskPlanExecutorFactoryTest extends Specification {
+    final TaskArtifactStateCacheAccess cache = Mock()
+    final DocumentationRegistry documentationRegistry = Mock()
+
+    def "creates a default executor"() {
+        when:
+        def factory = new TaskPlanExecutorFactory(cache, 0, documentationRegistry)
+
+        then:
+        factory.create().class == DefaultTaskPlanExecutor
+    }
+
+    def "creates a parallel executor"() {
+        when:
+        def factory = new TaskPlanExecutorFactory(cache, parallelExecuterCount, documentationRegistry)
+
+        then:
+        factory.create().class == ParallelTaskPlanExecutor
+
+        where:
+        parallelExecuterCount << [-1, 1, 3]
+    }
+}
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 fdcc978..049c178 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -71,6 +71,7 @@ public class DefaultCommandLineConverterTest {
     private boolean expectedOffline;
     private RefreshOptions expectedRefreshOptions = RefreshOptions.NONE;
     private boolean expectedRecompileScripts;
+    private int expectedParallelExecutorCount;
 
     @Test
     public void withoutAnyOptions() {
@@ -109,6 +110,7 @@ public class DefaultCommandLineConverterTest {
         assertEquals(expectedRefreshOptions, startParameter.getRefreshOptions());
         assertEquals(expectedRefreshDependencies, startParameter.isRefreshDependencies());
         assertEquals(expectedProjectCacheDir, startParameter.getProjectCacheDir());
+        assertEquals(expectedParallelExecutorCount, startParameter.getParallelThreadCount());
     }
 
     @Test
@@ -348,6 +350,7 @@ public class DefaultCommandLineConverterTest {
     public void withOffline() {
         expectedOffline = true;
         checkConversion("--offline");
+        checkConversion("-offline");
     }
 
     @Test
@@ -355,6 +358,7 @@ public class DefaultCommandLineConverterTest {
         expectedRefreshDependencies = true;
         expectedRefreshOptions = new RefreshOptions(asList(RefreshOptions.Option.DEPENDENCIES));
         checkConversion("--refresh-dependencies");
+        checkConversion("-refresh-dependencies");
     }
 
     @Test
@@ -385,4 +389,21 @@ public class DefaultCommandLineConverterTest {
         expectedTaskNames = toList("someTask", "--some-task-option");
         checkConversion("someTask", "--some-task-option");
     }
+
+    @Test
+    public void withParallelExecutor() {
+        expectedParallelExecutorCount = -1;
+        checkConversion("--parallel");
+    }
+
+    @Test
+    public void withParallelExecutorThreads() {
+        expectedParallelExecutorCount = 5;
+        checkConversion("--parallel-threads", "5");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withInvalidParallelExecutorThreads() {
+        checkConversion("--parallel-threads", "foo");
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
index 5b49ff0..f50ef15 100755
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
@@ -16,8 +16,8 @@
 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.LocationAwareException;
 import org.gradle.api.internal.MultiCauseException;
 import org.gradle.api.tasks.TaskExecutionException;
 import org.gradle.groovy.scripts.Script;
@@ -276,6 +276,13 @@ public class DefaultExceptionAnalyserTest {
         public List<? extends Throwable> getCauses() {
             return causes;
         }
+
+        public void initCauses(Iterable<? extends Throwable> causes) {
+            this.causes.clear();
+            for (Throwable cause : causes) {
+                this.causes.add(cause);
+            }
+        }
     }
 
     @Contextual
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 3942673..eb16a81 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -75,6 +75,7 @@ public class DefaultGradleLauncherTest {
     private ExceptionAnalyser exceptionAnalyserMock = context.mock(ExceptionAnalyser.class);
     private LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
     private ModelConfigurationListener modelListenerMock = context.mock(ModelConfigurationListener.class);
+    private TasksCompletionListener tasksCompletionListener = context.mock(TasksCompletionListener.class);
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
@@ -106,7 +107,7 @@ public class DefaultGradleLauncherTest {
 
         gradleLauncher = new DefaultGradleLauncher(gradleMock, initscriptHandlerMock, settingsHandlerMock,
                 buildLoaderMock, buildConfigurerMock, buildBroadcaster, exceptionAnalyserMock, loggingManagerMock,
-                modelListenerMock, buildExecuter);
+                modelListenerMock, tasksCompletionListener, buildExecuter);
 
         context.checking(new Expectations() {
             {
@@ -244,7 +245,7 @@ public class DefaultGradleLauncherTest {
             one(buildBroadcaster).projectsEvaluated(gradleMock);
             one(modelListenerMock).onConfigure(gradleMock);
             one(exceptionAnalyserMock).transform(failure);
-            will(returnValue(transformedException));
+             will(returnValue(transformedException));
             one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
         }});
 
@@ -299,6 +300,7 @@ public class DefaultGradleLauncherTest {
         context.checking(new Expectations() {
             {
                 one(buildExecuter).execute();
+                one(tasksCompletionListener).onTasksFinished(gradleMock);
             }
         });
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
index 2f762df..ff540a4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
@@ -29,6 +29,7 @@ import org.junit.Test
 import org.junit.runner.RunWith
 import static org.junit.Assert.*
 import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.ThreadGlobalInstantiator
 
 /**
  * @author Hans Dockter
@@ -56,7 +57,9 @@ class DefaultSettingsTest {
         gradleMock = context.mock(GradleInternal)
 
         projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
-        settings = new DefaultSettings(gradleMock, projectDescriptorRegistry, expectedClassLoader, settingsDir, scriptSourceMock, startParameter)
+        settings = ThreadGlobalInstantiator.orCreate.newInstance(DefaultSettings,
+                gradleMock, projectDescriptorRegistry, expectedClassLoader, settingsDir, scriptSourceMock, startParameter
+        )
     }
 
     @Test public void testSettings() {
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 ab872db..f9a12fe 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
@@ -16,7 +16,9 @@
 package org.gradle.initialization;
 
 import org.gradle.StartParameter;
+import org.gradle.api.internal.DynamicObjectAware;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.ThreadGlobalInstantiator;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.util.WrapUtil;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -48,7 +50,7 @@ public class SettingsFactoryTest {
         Map<String, String> expectedGradleProperties = WrapUtil.toMap("key", "myvalue");
         IProjectDescriptorRegistry expectedProjectDescriptorRegistry = new DefaultProjectDescriptorRegistry();
         StartParameter expectedStartParameter = new StartParameter();
-        SettingsFactory settingsFactory = new SettingsFactory(expectedProjectDescriptorRegistry);
+        SettingsFactory settingsFactory = new SettingsFactory(expectedProjectDescriptorRegistry, ThreadGlobalInstantiator.getOrCreate());
         final URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
         GradleInternal gradle = context.mock(GradleInternal.class);
 
@@ -58,7 +60,7 @@ public class SettingsFactoryTest {
         assertSame(gradle, settings.getGradle());
         assertSame(expectedProjectDescriptorRegistry, settings.getProjectDescriptorRegistry());
         for (Map.Entry<String, String> entry : expectedGradleProperties.entrySet()) {
-            assertEquals(entry.getValue(), settings.getDynamicObject().getProperty(entry.getKey()));
+            assertEquals(entry.getValue(), ((DynamicObjectAware)settings).getAsDynamicObject().getProperty(entry.getKey()));
         }
 
         assertSame(expectedSettingsDir, settings.getSettingsDir());
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/ConfigureLogging.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/ConfigureLogging.groovy
new file mode 100644
index 0000000..601981f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/ConfigureLogging.groovy
@@ -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.logging
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.Appender
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.Level
+import org.junit.rules.ExternalResource
+import org.slf4j.LoggerFactory
+import ch.qos.logback.classic.LoggerContext
+
+import java.util.logging.LogManager
+
+class ConfigureLogging extends ExternalResource {
+    private final Appender<ILoggingEvent> appender;
+    private Logger logger;
+
+    def ConfigureLogging(appender) {
+        this.appender = appender;
+    }
+
+    @Override
+    protected void before() {
+        attachAppender()
+    }
+
+    @Override
+    protected void after() {
+        detachAppender()
+    }
+
+    public void attachAppender() {
+        logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("ROOT");
+        logger.detachAndStopAllAppenders()
+        logger.addAppender(appender)
+        logger.setLevel(Level.ALL)
+    }
+
+    public void detachAppender() {
+        logger.detachAppender(appender)
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory()
+        lc.reset()
+        LogManager.getLogManager().reset()
+    }
+
+    public void setLevel(Level level) {
+        logger.setLevel(level)
+    }
+}
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 ef01f4d..c1b9570 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
@@ -16,46 +16,157 @@
 
 package org.gradle.logging
 
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.StandardOutputListener
 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 org.gradle.logging.internal.LoggingCommandLineConverter
 import org.gradle.util.RedirectStdOutAndErr
+import org.gradle.util.TextUtil
 import org.junit.Rule
+import org.slf4j.LoggerFactory
 import spock.lang.Specification
 
+import java.util.logging.Logger
+
 class LoggingServiceRegistryTest extends Specification {
+    final TestAppender appender = new TestAppender()
+    @Rule ConfigureLogging logging = new ConfigureLogging(appender)
     @Rule RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
-    final ServiceRegistry registry = new LoggingServiceRegistry()
 
     def providesALoggingManagerFactory() {
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+
         expect:
         def factory = registry.getFactory(LoggingManagerInternal.class)
         factory instanceof DefaultLoggingManagerFactory
     }
 
     def providesAStyledTextOutputFactory() {
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+
         expect:
         def factory = registry.get(StyledTextOutputFactory.class)
         factory instanceof DefaultStyledTextOutputFactory
     }
-    
+
     def providesAProgressLoggerFactory() {
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+
         expect:
         def factory = registry.get(ProgressLoggerFactory.class)
         factory instanceof DefaultProgressLoggerFactory
     }
 
     def providesACommandLineConverter() {
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+
         expect:
         def converter = registry.get(CommandLineConverter.class)
         converter instanceof LoggingCommandLineConverter
     }
 
-    def doesNotMessWithSystemOutAndErrUntilStarted() {
+    def resetsSlf4jWhenStarted() {
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+        def logger = LoggerFactory.getLogger("category")
+
+        when:
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+        logger.warn("before")
+
+        then:
+        appender.toString() == '[WARN before]'
+
+        when:
+        loggingManager.level = LogLevel.INFO
+        loggingManager.start()
+        logger.info("ignored")
+        logger.warn("warning")
+
+        then:
+        appender.toString() == '[WARN before]'
+    }
+
+    def routesSlf4jToListenersWhenStarted() {
+        StandardOutputListener listener = Mock()
+
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+        def logger = LoggerFactory.getLogger("category")
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+        loggingManager.addStandardOutputListener(listener)
+
+        when:
+        logger.warn("before")
+
+        then:
+        0 * listener._
+
+        when:
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+        logger.info("ignored")
+        logger.warn("warning")
+
+        then:
+        1 * listener.onOutput('warning')
+        1 * listener.onOutput(TextUtil.platformLineSeparator)
+        0 * listener._
+
+        when:
+        loggingManager.stop()
+        logger.warn("after")
+
+        then:
+        0 * listener._
+    }
+
+    def routesJavaUtilLoggingToListenersWhenStarted() {
+        StandardOutputListener listener = Mock()
+
+        given:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+        def logger = Logger.getLogger("category")
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+        loggingManager.addStandardOutputListener(listener)
+
+        when:
+        logger.warning("before")
+
+        then:
+        0 * listener._
+
+        when:
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+        logger.info("ignored")
+        logger.warning("warning")
+
+        then:
+        1 * listener.onOutput('warning')
+        1 * listener.onOutput(TextUtil.platformLineSeparator)
+        0 * listener._
+
+        when:
+        loggingManager.stop()
+        logger.warning("after")
+
+        then:
+        0 * listener._
+    }
+
+    def routesSystemOutAndErrToListenersWhenStarted() {
+        StandardOutputListener listener = Mock()
+
         when:
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
         def loggingManager = registry.newInstance(LoggingManagerInternal)
 
         then:
@@ -63,11 +174,101 @@ class LoggingServiceRegistryTest extends Specification {
         System.err == outputs.stdErrPrintStream
 
         when:
+        loggingManager.addStandardOutputListener(listener)
+        loggingManager.addStandardErrorListener(listener)
         loggingManager.start()
 
         then:
         System.out != outputs.stdOutPrintStream
         System.err != outputs.stdErrPrintStream
+
+        when:
+        System.out.println("info")
+        System.err.println("error")
+
+        then:
+        1 * listener.onOutput(TextUtil.toPlatformLineSeparators("info\n"))
+        1 * listener.onOutput(TextUtil.toPlatformLineSeparators("error\n"))
+        0 * listener._
+    }
+
+    def routesLoggingOutputToOriginalSystemOutAndErrWhenStarted() {
+        given:
+        def logger = LoggerFactory.getLogger("category")
+        def registry = LoggingServiceRegistry.newCommandLineProcessLogging()
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+
+        when:
+        logger.warn("before")
+        logger.error("before")
+
+        then:
+        outputs.stdOut == ''
+        outputs.stdErr == ''
+
+        when:
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+        logger.warn("warning")
+        logger.error("error")
+
+        then:
+        outputs.stdOut == TextUtil.toPlatformLineSeparators('warning\n')
+        outputs.stdErr == TextUtil.toPlatformLineSeparators('error\n')
+    }
+
+    def routesSlf4jWhenEmbedded() {
+        given:
+        def registry = LoggingServiceRegistry.newEmbeddableLogging()
+        def logger = LoggerFactory.getLogger("category")
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+
+        when:
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+        logger.warn("warning")
+        logger.error("error")
+
+        then:
+        outputs.stdOut == TextUtil.toPlatformLineSeparators('warning\n')
+        outputs.stdErr == TextUtil.toPlatformLineSeparators('error\n')
+    }
+
+    def doesNotMessWithJavaUtilLoggingWhenEmbedded() {
+        given:
+        def registry = LoggingServiceRegistry.newEmbeddableLogging()
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+        def logger = Logger.getLogger("category")
+
+        when:
+        logger.warning("warning")
+        logger.severe("error")
+
+        then:
+        outputs.stdOut == ''
+        outputs.stdErr == ''
+    }
+
+    def doesNotMessWithSystemOutputAndErrorWhenEmbedded() {
+        when:
+        def registry = LoggingServiceRegistry.newEmbeddableLogging()
+        def loggingManager = registry.newInstance(LoggingManagerInternal)
+        loggingManager.level = LogLevel.WARN
+        loggingManager.start()
+
+        then:
+        System.out == outputs.stdOutPrintStream
+        System.err == outputs.stdErrPrintStream
+    }
+
+    def canCreateANestedRegistry() {
+        given:
+        def registry = LoggingServiceRegistry.newEmbeddableLogging()
+
+        expect:
+        def child = registry.newLogging()
+        child != null
     }
-    
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/LoggingTestHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/LoggingTestHelper.groovy
deleted file mode 100644
index f335bed..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/logging/LoggingTestHelper.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.logging
-
-import ch.qos.logback.classic.spi.ILoggingEvent
-import ch.qos.logback.core.Appender
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.Level
-import org.slf4j.LoggerFactory
-import ch.qos.logback.classic.LoggerContext
-
-/**
- * Should be a rule, but doesn't work with jmock.
- */
-class LoggingTestHelper {
-    private final Appender<ILoggingEvent> appender;
-    private Logger logger;
-
-    def LoggingTestHelper(appender) {
-        this.appender = appender;
-    }
-
-    public void attachAppender() {
-        logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("ROOT");
-        logger.detachAndStopAllAppenders()
-        logger.addAppender(appender)
-        logger.setLevel(Level.ALL)
-    }
-
-    public void detachAppender() {
-        logger.detachAppender(appender)
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory()
-        lc.reset()
-    }
-
-    public void setLevel(Level level) {
-        logger.setLevel(level)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/TestAppender.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/TestAppender.groovy
new file mode 100644
index 0000000..e62fce0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/TestAppender.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.logging
+
+import ch.qos.logback.core.AppenderBase
+
+class TestAppender<LoggingEvent> extends AppenderBase<LoggingEvent> {
+    final StringWriter writer = new StringWriter()
+
+    synchronized void doAppend(LoggingEvent e) {
+        append(e)
+    }
+
+    @Override
+    String toString() {
+        return writer.toString()
+    }
+
+    protected void append(LoggingEvent e) {
+        writer.append("[")
+        writer.append(e.level.toString())
+        writer.append(' ')
+        writer.append(e.formattedMessage)
+        writer.append("]")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleBackedProgressRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleBackedProgressRendererTest.groovy
index 3e131cd..6266ec0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleBackedProgressRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleBackedProgressRendererTest.groovy
@@ -15,11 +15,14 @@
  */
 package org.gradle.logging.internal
 
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData
+
 class ConsoleBackedProgressRendererTest extends OutputSpecification {
     private final OutputEventListener listener = Mock()
     private final Console console = Mock()
     private final Label statusBar = Mock()
-    private final ConsoleBackedProgressRenderer renderer = new ConsoleBackedProgressRenderer(listener, console)
+    private final StatusBarFormatter statusBarFormatter = new DefaultStatusBarFormatter(Mock(ConsoleMetaData))
+    private final ConsoleBackedProgressRenderer renderer = new ConsoleBackedProgressRenderer(listener, console, statusBarFormatter)
 
     def setup() {
         (0..1) * console.getStatusBar() >> statusBar
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStatusBarFormatterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStatusBarFormatterTest.groovy
new file mode 100644
index 0000000..0a97f3d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStatusBarFormatterTest.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.logging.internal
+
+import spock.lang.Specification
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData
+
+class DefaultStatusBarFormatterTest extends Specification {
+
+    ConsoleMetaData consoleMetaData = Mock()
+    private final StatusBarFormatter statusBarFormatter = new DefaultStatusBarFormatter(consoleMetaData)
+
+    def "formats multiple operations"(){
+        expect:
+        "> status1 > status2" == statusBarFormatter.format(Arrays.asList(new ConsoleBackedProgressRenderer.Operation("shortDescr1", "status1"), new ConsoleBackedProgressRenderer.Operation("shortDescr2", "status2")))
+    }
+
+    def "uses shortDescr if no status available"(){
+        expect:
+        "> shortDescr1" == statusBarFormatter.format(Arrays.asList(new ConsoleBackedProgressRenderer.Operation("shortDescr1", null)))
+        "> shortDescr2" == statusBarFormatter.format(Arrays.asList(new ConsoleBackedProgressRenderer.Operation("shortDescr2", '')))
+    }
+
+    def "trims output to one less than the max console width"(){
+        when:
+        _ * consoleMetaData.getCols() >> 10
+        then:
+        "> these a" == statusBarFormatter.format(Arrays.asList(new ConsoleBackedProgressRenderer.Operation("shortDescr1", "these are more than 10 characters")))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/JavaUtilLoggingConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/JavaUtilLoggingConfigurerTest.groovy
index 32887df..f9e4a79 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/JavaUtilLoggingConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/JavaUtilLoggingConfigurerTest.groovy
@@ -16,36 +16,25 @@
 
 package org.gradle.logging.internal
 
-import spock.lang.Specification
-import java.util.logging.LogManager
-import ch.qos.logback.classic.spi.ILoggingEvent
-import ch.qos.logback.core.Appender
 import org.gradle.api.logging.LogLevel
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.junit.Rule
+import spock.lang.Specification
+
 import java.util.logging.Logger
-import ch.qos.logback.classic.Level
-import org.gradle.logging.LoggingTestHelper
 
 class JavaUtilLoggingConfigurerTest extends Specification {
-    private final Appender<ILoggingEvent> appender = Mock()
-    private final LoggingTestHelper helper = new LoggingTestHelper(appender)
+    final TestAppender appender = new TestAppender()
+    @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
     private final JavaUtilLoggingConfigurer configurer = new JavaUtilLoggingConfigurer()
 
-    def setup() {
-        helper.attachAppender()
-    }
-
-    def cleanup() {
-        helper.detachAppender()
-        LogManager.getLogManager().reset()
-    }
-
     def routesJulToSlf4j() {
         when:
         configurer.configure(LogLevel.DEBUG)
         Logger.getLogger('test').info('info message')
 
         then:
-        1 * appender.doAppend({ILoggingEvent event -> event.level == Level.INFO && event.message == 'info message'})
-        0 * appender._
+        appender.toString() == '[INFO info message]'
     }
 }
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 cbd0041..b390b5d 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
@@ -15,26 +15,28 @@
  */
 package org.gradle.logging.internal
 
+import org.gradle.api.Action
 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.internal.nativeplatform.console.ConsoleMetaData
 
 class OutputEventRendererTest extends OutputSpecification {
     @Rule public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
     private final ConsoleStub console = new ConsoleStub()
+    private final ConsoleMetaData metaData = Mock()
+    private final Action<OutputEventRenderer> consoleConfigureAction = Mock()
     private OutputEventRenderer renderer
 
     def setup() {
-        renderer = new OutputEventRenderer(Mock(TerminalDetector))
-        renderer.addStandardOutput(outputs.stdOutPrintStream)
-        renderer.addStandardError(outputs.stdErrPrintStream)
+        renderer = new OutputEventRenderer(consoleConfigureAction)
         renderer.configure(LogLevel.INFO)
     }
 
     def rendersLogEventsToStdOut() {
         when:
+        renderer.addStandardOutputAndError()
         renderer.onOutput(event('message', LogLevel.INFO))
 
         then:
@@ -44,6 +46,7 @@ class OutputEventRendererTest extends OutputSpecification {
 
     def rendersErrorLogEventsToStdErr() {
         when:
+        renderer.addStandardOutputAndError()
         renderer.onOutput(event('message', LogLevel.ERROR))
 
         then:
@@ -52,13 +55,27 @@ class OutputEventRendererTest extends OutputSpecification {
     }
 
     def rendersLogEventsWhenLogLevelIsDebug() {
+        def listener = new TestListener()
+
         when:
         renderer.configure(LogLevel.DEBUG)
+        renderer.addStandardOutputListener(listener)
         renderer.onOutput(event(tenAm, 'message', LogLevel.INFO))
 
         then:
-        outputs.stdOut.readLines() == ['10:00:00.000 [INFO] [category] message']
-        outputs.stdErr == ''
+        listener.value.readLines() == ['10:00:00.000 [INFO] [category] message']
+    }
+
+    def rendersLogEventsToStdOutandStdErrWhenLogLevelIsDebug() {
+        when:
+        renderer.configure(LogLevel.DEBUG)
+        renderer.addStandardOutputAndError()
+        renderer.onOutput(event(tenAm, 'info', LogLevel.INFO))
+        renderer.onOutput(event(tenAm, 'error', LogLevel.ERROR))
+
+        then:
+        outputs.stdOut.readLines() == ['10:00:00.000 [INFO] [category] info']
+        outputs.stdErr.readLines() == ['10:00:00.000 [ERROR] [category] error']
     }
 
     def rendersLogEventsToStdOutListener() {
@@ -97,7 +114,7 @@ class OutputEventRendererTest extends OutputSpecification {
         then:
         listener.value.readLines() == ['10:00:00.000 [INFO] [category] message']
     }
-    
+
     def rendersErrorLogEventsToStdErrListener() {
         def listener = new TestListener()
 
@@ -167,6 +184,7 @@ class OutputEventRendererTest extends OutputSpecification {
 
     def rendersProgressEvents() {
         when:
+        renderer.addStandardOutputAndError()
         renderer.onOutput(start(loggingHeader: 'description'))
         renderer.onOutput(complete('status'))
 
@@ -177,6 +195,7 @@ class OutputEventRendererTest extends OutputSpecification {
 
     def doesNotRendersProgressEventsForLogLevelQuiet() {
         when:
+        renderer.addStandardOutputAndError()
         renderer.configure(LogLevel.QUIET)
         renderer.onOutput(start('description'))
         renderer.onOutput(complete('status'))
@@ -186,8 +205,8 @@ class OutputEventRendererTest extends OutputSpecification {
         outputs.stdErr == ''
     }
 
-    def rendersLogEventsWhenStdOutAndStdErrAreTerminal() {
-        renderer.addConsole(console, true, true)
+    def rendersLogEventsWhenStdOutAndStdErrAreConsole() {
+        renderer.addConsole(console, true, true, metaData)
 
         when:
         renderer.onOutput(start(loggingHeader: 'description'))
@@ -199,8 +218,8 @@ class OutputEventRendererTest extends OutputSpecification {
         console.value.readLines() == ['description', 'info', '{error}error', '{normal}description {progressstatus}status{normal}']
     }
 
-    def rendersLogEventsWhenOnlyStdOutIsTerminal() {
-        renderer.addConsole(console, true, false)
+    def rendersLogEventsWhenOnlyStdOutIsConsole() {
+        renderer.addConsole(console, true, false, metaData)
 
         when:
         renderer.onOutput(start(loggingHeader: 'description'))
@@ -212,8 +231,8 @@ class OutputEventRendererTest extends OutputSpecification {
         console.value.readLines() == ['description', 'info', 'description {progressstatus}status{normal}']
     }
 
-    def rendersLogEventsWhenOnlyStdErrIsTerminal() {
-        renderer.addConsole(console, false, true)
+    def rendersLogEventsWhenOnlyStdErrIsConsole() {
+        renderer.addConsole(console, false, true, metaData)
 
         when:
         renderer.onOutput(start('description'))
@@ -224,6 +243,57 @@ class OutputEventRendererTest extends OutputSpecification {
         then:
         console.value.readLines() == ['{error}error', '{normal}']
     }
+
+    def rendersLogEventsInConsoleWhenLogLevelIsDebug() {
+        renderer.configure(LogLevel.DEBUG)
+        renderer.addConsole(console, true, true, metaData)
+
+        when:
+        renderer.onOutput(event(tenAm, 'info', LogLevel.INFO))
+        renderer.onOutput(event(tenAm, 'error', LogLevel.ERROR))
+
+        then:
+        console.value.readLines() == ['10:00:00.000 [INFO] [category] info', '{error}10:00:00.000 [ERROR] [category] error', '{normal}']
+    }
+
+    def attachesConsoleWhenStdOutAndStdErrAreAttachedToConsole() {
+        when:
+        renderer.addStandardOutputAndError()
+        renderer.addConsole(console, true, true, metaData)
+        renderer.onOutput(event('info', LogLevel.INFO))
+        renderer.onOutput(event('error', LogLevel.ERROR))
+
+        then:
+        console.value.readLines() == ['info', '{error}error', '{normal}']
+        outputs.stdOut == ''
+        outputs.stdErr == ''
+    }
+
+    def attachesConsoleWhenOnlyStdOutIsAttachedToConsole() {
+        when:
+        renderer.addStandardOutputAndError()
+        renderer.addConsole(console, true, false, metaData)
+        renderer.onOutput(event('info', LogLevel.INFO))
+        renderer.onOutput(event('error', LogLevel.ERROR))
+
+        then:
+        console.value.readLines() == ['info']
+        outputs.stdOut == ''
+        outputs.stdErr.readLines() == ['error']
+    }
+
+    def attachesConsoleWhenOnlyStdErrIsAttachedToConsole() {
+        when:
+        renderer.addStandardOutputAndError()
+        renderer.addConsole(console, false, true, metaData)
+        renderer.onOutput(event('info', LogLevel.INFO))
+        renderer.onOutput(event('error', LogLevel.ERROR))
+
+        then:
+        console.value.readLines() == ['{error}error', '{normal}']
+        outputs.stdOut.readLines() == ['info']
+        outputs.stdErr == ''
+    }
 }
 
 class TestListener implements StandardOutputListener {
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputSpecification.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputSpecification.groovy
index 5cb6cc9..77fa55e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputSpecification.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputSpecification.groovy
@@ -70,6 +70,6 @@ class OutputSpecification extends Specification {
     }
 
     ProgressCompleteEvent complete(String status) {
-        return new ProgressCompleteEvent(tenAm, 'category', status)
+        return new ProgressCompleteEvent(tenAm, 'category', 'description', status)
     }
 }
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
deleted file mode 100755
index 6a0937d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.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.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
- */
-class TerminalDetectorFactoryTest extends Specification {
-    @Rule TemporaryFolder temp
-
-    @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/process/internal/DefaultExecHandleSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
index 09d72c6..d40fb1b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
@@ -147,7 +147,7 @@ class DefaultExecHandleSpec extends Specification {
         execHandle.abort()
     }
 
-    @Ignore //TODO SF not yet implemented
+    @Ignore //TODO SF not yet implemented, as following @Ignores
     void "aborts daemon"() {
         def output = new ByteArrayOutputStream()
         def execHandle = handle().setDaemon(true).setStandardOutput(output).args(args(SlowDaemonApp.class)).build();
@@ -186,7 +186,7 @@ class DefaultExecHandleSpec extends Specification {
         execHandle.abort()
     }
 
-    @Ignore //TODO SF not yet implemented
+    @Ignore
     void "can detach from long daemon and then wait for finish"() {
         def out = new ByteArrayOutputStream()
         def execHandle = handle().setStandardOutput(out).args(args(SlowDaemonApp.class, "200")).build();
@@ -205,7 +205,7 @@ class DefaultExecHandleSpec extends Specification {
         execHandle.state == ExecHandleState.SUCCEEDED
     }
 
-    @Ignore //TODO SF not yet implemented
+    @Ignore
     void "can detach from fast app then wait for finish"() {
         def out = new ByteArrayOutputStream()
         def execHandle = handle().setStandardOutput(out).args(args(TestApp.class)).build();
@@ -220,7 +220,7 @@ class DefaultExecHandleSpec extends Specification {
     }
 
     @Ignore
-    //TODO SF. I have a feeling it is not really testable cleanly.
+    //it may not be easily testable
     void "detach detects when process did not start or died prematurely"() {
         def execHandle = handle().args(args(BrokenApp.class)).build();
 
@@ -264,7 +264,6 @@ class DefaultExecHandleSpec extends Specification {
     }
 
     @Timeout(2)
-    //TODO SF not yet implemented
     @Ignore
     void "exec handle can detach with timeout"() {
         given:
@@ -279,7 +278,6 @@ class DefaultExecHandleSpec extends Specification {
         //the timeout does not hit
     }
 
-    //TODO SF not yet implemented
     @Ignore
     void "exec handle can wait with timeout"() {
         given:
@@ -303,29 +301,6 @@ class DefaultExecHandleSpec extends Specification {
         }
     }
 
-    @Ignore
-    //TODO SF add coverage (or move somewhere else) - it should over the ibm+windows use case
-    void "consumes input"() {
-        given:
-        def bytes = new ByteArrayOutputStream()
-        def object = new ObjectOutputStream(bytes)
-        object.writeObject(new Prints(message: 'yummie input'))
-        object.flush()
-        object.close()
-        def out = new ByteArrayOutputStream()
-
-        def execHandle = handle().setStandardOutput(out).setStandardInput(new ByteArrayInputStream(bytes.toByteArray())).args(args(InputReadingApp.class)).build();
-
-        when:
-        execHandle.start()
-        def result = execHandle.waitForFinish()
-
-        then:
-        result.rethrowFailure()
-        result.exitValue == 0
-        out.toString().contains('yummie input')
-    }
-
     private ExecHandleBuilder handle() {
         new ExecHandleBuilder()
                 .executable(Jvm.current().getJavaExecutable().getAbsolutePath())
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 8d42177..b88f61a 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
@@ -48,7 +48,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
- at Ignore //TODO SF refactor this bastard to spock
+ at Ignore
 public class DefaultWorkerProcessFactoryTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final MessagingServer messagingServer = context.mock(MessagingServer.class);
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
index 5288956..1a3d771 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
@@ -64,6 +64,8 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
 
         context.checking {
             one(execHandle).start()
+            one(execHandle).getState()
+            will(returnValue(ExecHandleState.STARTED))
         }
 
         expectTimesOut(1, TimeUnit.SECONDS) {
@@ -71,7 +73,7 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
                 workerProcess.start()
                 fail()
             } catch (ExecException e) {
-                assertThat(e.message, equalTo("Timeout after waiting 1.0 seconds for $execHandle to connect." as String))
+                assertThat(e.message, equalTo("Timeout after waiting 1.0 seconds for $execHandle (STARTED, running: true) to connect." as String))
             }
         }
     }
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
old mode 100644
new mode 100755
index a86c2b8..9f2837f
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
@@ -27,7 +27,8 @@ import java.nio.charset.Charset
  * by Szczepan Faber, created at: 2/13/12
  */
 class JvmOptionsTest extends Specification {
-    
+    final String defaultCharset = Charset.defaultCharset().name()
+
     def "reads options from String"() {
         expect:
         JvmOptions.fromString("") == []
@@ -41,19 +42,19 @@ class JvmOptionsTest extends Specification {
         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("  -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! "
+        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("-Dfoo= -Dbar -Dbaz=\"\"").getSystemProperties() == [foo: '', bar: '', baz: '']
         parse("-XXfoo=").allJvmArgs.contains('-XXfoo=')
         parse("-XXbar=\"\"").allJvmArgs.contains('-XXbar=')
     }
@@ -73,10 +74,35 @@ class JvmOptionsTest extends Specification {
         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"() {
+    def "debug option can be set via allJvmArgs"() {
+        setup:
+        def opts = createOpts()
+
+        when:
+        opts.allJvmArgs = ['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
+        then:
+        opts.debug
+
+        when:
+        opts.allJvmArgs = []
+        then:
+        opts.debug == false
+    }
+
+    def "managed jvm args includes heap settings"() {
         expect:
-        parse("-Xms1G -XX:-PrintClassHistogram -Dfile.encoding=UTF-8 -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Dfile.encoding=UTF-8"]
-        parse("-Xms1G -XX:-PrintClassHistogram -Xmx2G -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Xmx2G", "-Dfile.encoding=UTF-8"]
+        parse("-Xms1G -XX:-PrintClassHistogram -Xmx2G -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Xmx2G", "-Dfile.encoding=${defaultCharset}"]
+    }
+
+    def "managed jvm args includes file encoding"() {
+        expect:
+        parse("-XX:-PrintClassHistogram -Dfile.encoding=klingon-16 -Dfoo.encoding=blah").managedJvmArgs == ["-Dfile.encoding=klingon-16"]
+        parse("-XX:-PrintClassHistogram -Dfoo.encoding=blah").managedJvmArgs == ["-Dfile.encoding=${defaultCharset}"]
+    }
+
+    def "managed jvm args includes JMX settings"() {
+        expect:
+        parse("-Dfile.encoding=utf-8 -Dcom.sun.management.jmxremote").managedJvmArgs == ["-Dcom.sun.management.jmxremote", "-Dfile.encoding=utf-8"]
     }
 
     def "file encoding can be set as systemproperty"() {
@@ -95,6 +121,14 @@ class JvmOptionsTest extends Specification {
         opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-1");
     }
 
+    def "uses system default file encoding when null is used"() {
+        JvmOptions opts = createOpts()
+        when:
+        opts.defaultCharacterEncoding = null
+        then:
+        opts.allJvmArgs.contains("-Dfile.encoding=${defaultCharset}".toString());
+    }
+
     def "last file encoding definition is used"() {
         JvmOptions opts = createOpts()
         when:
@@ -113,28 +147,51 @@ class JvmOptionsTest extends Specification {
     }
 
     def "file.encoding arg has default value"() {
-        String defaultCharset = Charset.defaultCharset().name()
         expect:
         createOpts().allJvmArgs.contains("-Dfile.encoding=${defaultCharset}".toString());
     }
 
-    def "copyTo respects defaultFileEncoding"(){
+    def "copyTo respects defaultFileEncoding"() {
         JavaForkOptions target = Mock(JavaForkOptions)
         when:
         parse("-Dfile.encoding=UTF-8 -Dfoo.encoding=blah -Dfile.encoding=UTF-16").copyTo(target)
         then:
-        1 * target.setDefaultCharacterEncoding( "UTF-16")
+        1 * target.systemProperties({it == ["file.encoding": "UTF-16"]})
+    }
+
+    def "can enter debug mode"() {
+        def opts = createOpts()
+        when:
+        opts.debug = true
+        then:
+        opts.debug
+    }
+
+    def "can enter debug mode after setting other options"() {
+        def opts = createOpts()
+        when:
+        opts.jvmArgs(JvmOptions.fromString('-Xmx1G -Xms1G'))
+        opts.debug = true
+        then:
+        opts.allJvmArgs.containsAll(['-Xmx1G', '-Xms1G', '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'])
+    }
+
+    def "can enter debug mode before setting other options"() {
+        def opts = createOpts()
+        opts.debug = true
+        when:
+        opts.jvmArgs(JvmOptions.fromString('-Xmx1G -Xms1G'))
+        then:
+        opts.allJvmArgs.containsAll(['-Xmx1G', '-Xms1G', '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'])
     }
 
     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/util/CollectionUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
deleted file mode 100644
index 80a2055..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
+++ /dev/null
@@ -1,91 +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 org.gradle.api.Transformer
-import org.gradle.api.specs.Specs
-import spock.lang.Specification
-import static org.gradle.util.CollectionUtils.*
-
-class CollectionUtilsTest extends Specification {
-
-    def "list filtering"() {
-        given:
-        def spec = Specs.convertClosureToSpec { it < 5 }
-        def filter = { Integer[] nums -> 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 -> 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 -> 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:
-        toStringList([]) == []
-        toStringList(list) == ["42", "string"]
-    }
-
-    def "list compacting"() {
-        expect:
-        compact([1, null, 2]) == [1, 2]
-        compact([null, 1, 2]) == [1, 2]
-        compact([1, 2, null]) == [1, 2]
-
-        def l = [1, 2, 3]
-        compact(l).is l
-
-    }
-
-    def "list stringize"() {
-        expect:
-        stringize([1,2,3]) == ["1", "2", "3"]
-        stringize([]) == []
-    }
-
-    def "stringize"() {
-        expect:
-        stringize(["c", "b", "a"], new TreeSet<String>()) == ["a", "b", "c"] as Set
-    }
-
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy
new file mode 100644
index 0000000..0d581f0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Factory
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.junit.Rule
+import spock.lang.Specification
+
+class DeprecationLoggerTest extends Specification {
+    final TestAppender appender = new TestAppender()
+    @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
+
+    public void cleanup() {
+        DeprecationLogger.reset()
+    }
+
+    def "logs deprecation warning once"() {
+        when:
+        DeprecationLogger.nagUserWith("nag")
+        DeprecationLogger.nagUserWith("nag")
+
+        then:
+        appender.toString() == '[WARN nag]'
+    }
+
+    def "does not log warning while disabled with factory"() {
+        Factory<String> factory = Mock()
+
+        when:
+        def result = DeprecationLogger.whileDisabled(factory)
+
+        then:
+        result == 'result'
+
+        and:
+        1 * factory.create() >> {
+            DeprecationLogger.nagUserWith("nag")
+            return "result"
+        }
+        0 * _._
+    }
+
+    def "does not log warning while disabled with action"() {
+        Runnable action = Mock()
+
+        when:
+        DeprecationLogger.whileDisabled(action)
+
+        then:
+        1 * action.run()
+        0 * _._
+    }
+
+    def "deprecation message has next major version"() {
+        given:
+        def major = GradleVersion.current().major
+
+        expect:
+        major != -1
+
+        when:
+        DeprecationLogger.nagUserOfDeprecated("foo", "bar")
+
+        then:
+        appender.toString() == "[WARN foo has been deprecated and is scheduled to be removed in Gradle ${major + 1}.0. bar.]"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DisconnectableInputStreamTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DisconnectableInputStreamTest.groovy
index e875dfe..5cbf463 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/DisconnectableInputStreamTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/DisconnectableInputStreamTest.groovy
@@ -15,10 +15,16 @@
  */
 package org.gradle.util
 
-import java.util.concurrent.CopyOnWriteArrayList
+import org.gradle.api.Action
+import org.gradle.internal.concurrent.ExecutorFactory
 import org.jmock.integration.junit4.JMock
 import org.junit.Test
 import org.junit.runner.RunWith
+
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
 import static org.hamcrest.Matchers.equalTo
 import static org.hamcrest.Matchers.greaterThan
 import static org.junit.Assert.assertThat
@@ -27,9 +33,17 @@ import static org.junit.Assert.assertThat
 class DisconnectableInputStreamTest extends MultithreadedTestCase {
     final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
 
+    Action<Runnable> toActionExecuter(ExecutorFactory factory) {
+        new Action<Runnable>() {
+            void execute(Runnable runnable) {
+                factory.create("test executer").execute(runnable)
+            }
+        }
+    }
+
     @Test
     public void inputStreamReadsFromSourceInputStream() {
-        def instr = new DisconnectableInputStream(stream("some text"), executorFactory)
+        def instr = new DisconnectableInputStream(stream("some text"), toActionExecuter(executorFactory))
 
         assertReads(instr, "some text")
 
@@ -41,7 +55,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
 
     @Test
     public void buffersDataReadFromSourceInputStream() {
-        def instr = new DisconnectableInputStream(stream("test1test2end"), executorFactory)
+        def instr = new DisconnectableInputStream(stream("test1test2end"), toActionExecuter(executorFactory))
 
         assertReads(instr, "test1")
         assertReads(instr, "test2")
@@ -55,11 +69,11 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
 
     @Test
     public void canReadSingleChars() {
-        def instr = new DisconnectableInputStream(stream("abc"), executorFactory)
+        def instr = new DisconnectableInputStream(stream("abc"), toActionExecuter(executorFactory))
 
-        assertThat((char)instr.read(), equalTo('a'.charAt(0)))
-        assertThat((char)instr.read(), equalTo('b'.charAt(0)))
-        assertThat((char)instr.read(), equalTo('c'.charAt(0)))
+        assertThat((char) instr.read(), equalTo('a'.charAt(0)))
+        assertThat((char) instr.read(), equalTo('b'.charAt(0)))
+        assertThat((char) instr.read(), equalTo('c'.charAt(0)))
         assertThat(instr.read(), equalTo(-1))
 
         instr.close()
@@ -67,7 +81,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
 
     @Test
     public void canReadUsingZeroLengthBuffer() {
-        def instr = new DisconnectableInputStream(stream("abc"), executorFactory)
+        def instr = new DisconnectableInputStream(stream("abc"), toActionExecuter(executorFactory))
 
         assertThat(instr.read(new byte[0], 0, 0), equalTo(0))
         assertReads(instr, "abc")
@@ -96,7 +110,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return 2
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory, 10)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory), 10)
 
         run {
             syncAt(1)
@@ -122,7 +136,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return expected.length
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory))
         run {
             expectBlocksUntil(1) {
                 assertReads(instr, "some text")
@@ -144,7 +158,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return count
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory))
 
         start {
             expectBlocksUntil(1) {
@@ -169,7 +183,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return -1
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory))
 
         run {
             expectBlocksUntil(1) {
@@ -190,7 +204,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             throw failure
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory))
 
         run {
             def nread = instr.read(new byte[20])
@@ -211,7 +225,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return -1
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory, 10)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory), 10)
 
         run {
             syncAt(1)
@@ -222,7 +236,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
 
         instr.close()
     }
-    
+
     @Test
     public void readerThreadStopsReadingAfterClose() {
         def source = stream()
@@ -230,7 +244,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
             return count
         }
 
-        def instr = new DisconnectableInputStream(source, executorFactory)
+        def instr = new DisconnectableInputStream(source, toActionExecuter(executorFactory))
         instr.read()
         instr.close()
 
@@ -239,7 +253,7 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
 
     @Test
     public void cannotReadFromInputStreamAfterItIsClosed() {
-        def instr = new DisconnectableInputStream(stream("some text"), executorFactory)
+        def instr = new DisconnectableInputStream(stream("some text"), toActionExecuter(executorFactory))
         instr.close()
 
         assertThat(instr.read(), equalTo(-1))
@@ -247,6 +261,21 @@ class DisconnectableInputStreamTest extends MultithreadedTestCase {
         assertThat(instr.read(new byte[10], 2, 5), equalTo(-1))
     }
 
+    @Test
+    void threadExecuterExecutesTheAction() {
+        def e = new DisconnectableInputStream.ThreadExecuter()
+        def latch = new CountDownLatch(1)
+
+        e.execute {
+            def t = Thread.currentThread()
+            assert t.daemon
+            latch.countDown()
+        }
+
+        assert latch.await(10, TimeUnit.SECONDS)
+    }
+
+
     def assertReads(InputStream instr, String expected) {
         def expectedBytes = expected.bytes
         def buffer = new byte[expectedBytes.length]
@@ -288,4 +317,5 @@ class ActionInputStream extends InputStream {
     int read() {
         throw new UnsupportedOperationException()
     }
+
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
index 084e643..bc1a8da 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
@@ -19,11 +19,15 @@ package org.gradle.util
 import org.junit.Rule
 import spock.lang.Specification
 
+import static org.gradle.util.GFileUtils.mkdirs
+import static org.gradle.util.GFileUtils.parentMkdirs
+import org.gradle.api.UncheckedIOException
+
 /**
  * by Szczepan Faber, created at: 2/28/12
  */
 class GFileUtilsTest extends Specification {
-    
+
     @Rule TemporaryFolder temp
 
     def "can read the file's tail"() {
@@ -52,4 +56,68 @@ three
         noExceptionThrown()
         dir.exists()
     }
+
+    def "relative path"() {
+        when:
+        def from = new File(fromPath)
+        def to = new File(toPath)
+
+        then:
+        GFileUtils.relativePath(from, to) == path
+
+        where:
+        fromPath | toPath  | path
+        "a"      | "a/b"   | "b"
+        "a"      | "a/b/a" | "b/a"
+        "a"      | "b"     | "../b"
+        "a/b"    | "b"     | "../../b"
+        "a"      | "a"     | ""
+    }
+
+    def "can mkdirs"() {
+        given:
+        def f = temp.file("a/b/c/d")
+
+        expect:
+        !f.isDirectory()
+
+        when:
+        mkdirs(f)
+
+        then:
+        f.isDirectory()
+    }
+
+    def "can parentMkdirs"() {
+        given:
+        def f = temp.file("a/b/c/d")
+
+        expect:
+        !f.parentFile.exists()
+
+        when:
+        def p = parentMkdirs(f)
+
+        then:
+        p.isDirectory()
+        f.parentFile == p
+    }
+
+    def "mkdirs fails if can't make parent"() {
+        given:
+        def e = temp.file("a/b/c/d/e")
+        def b = temp.createFile("a/b")
+        def c = temp.file("a/b/c")
+
+        expect:
+        b.file
+
+        when:
+        mkdirs(e)
+
+        then:
+        def ex = thrown UncheckedIOException
+        ex.message == "Cannot create parent directory '$c' when creating directory '$e' as '$b' is not a directory"
+    }
+
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
index 155c776..84fc2d3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
@@ -19,6 +19,7 @@ package org.gradle.util;
 import static org.gradle.util.GUtil.*
 
 public class GUtilTest extends spock.lang.Specification {
+    static sep = File.pathSeparator
     
     def convertStringToCamelCase() {
         expect:
@@ -153,4 +154,9 @@ public class GUtilTest extends spock.lang.Specification {
         expect:
         flattenElements(1, [2,3]) == [1,2,3]
     }
+
+    def "convert to path notation"() {
+        expect:
+        asPath(["lib1.jar", "lib2.jar", new File("lib3.jar")]) == "lib1.jar${sep}lib2.jar${sep}lib3.jar"
+    }
 }
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 d037895..70ac33d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -30,6 +30,13 @@ import spock.lang.Specification
 class GradleVersionTest extends Specification {
     final GradleVersion version = GradleVersion.current()
 
+    def "valid versions"() {
+        expect:
+        version.valid
+        !GradleVersion.version("asdfasdfas").valid
+        GradleVersion.version("1.0").valid
+    }
+
     def currentVersionHasNonNullVersion() {
         expect:
         version.version
@@ -219,6 +226,32 @@ class GradleVersionTest extends Specification {
         '0.0'                     | '0.9.2'
     }
 
+    def "can get version base"() {
+        expect:
+        GradleVersion.version(v).versionBase == base
+
+        where:
+        v                         | base
+        "1.0"                     | "1.0"
+        "1.0-rc-1"                | "1.0"
+        '0.9-20101220100000+1000' | "0.9"
+        '0.9-20101220100000'      | "0.9"
+        "asdfasd"                 | null
+    }
+
+    def "can get version major"() {
+        expect:
+        GradleVersion.version(v).major == major
+
+        where:
+        v                         | major
+        "1.0"                     | 1
+        "1.0-rc-1"                | 1
+        '0.9-20101220100000+1000' | 0
+        '0.9-20101220100000'      | 0
+        "asdfasd"                 | -1
+    }
+
     def prettyPrint() {
         String expectedText = """
 ------------------------------------------------------------
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 71bb37a..307702e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
@@ -31,7 +31,7 @@ import java.io.IOException;
 public class LineBufferingOutputStreamTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private Action<String> action = context.mock(Action.class);
-    private LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, false, 8);
+    private LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, 8);
     private String eol;
 
     @Before
@@ -47,43 +47,32 @@ public class LineBufferingOutputStreamTest {
     @Test
     public void logsEachLineAsASeparateLogMessage() throws IOException {
         context.checking(new Expectations() {{
-            one(action).execute("line 1");
-            one(action).execute("line 2");
-        }});
-
-        outputStream.write(String.format("line 1%nline 2%n").getBytes());
-    }
-
-    @Test
-    public void canReceiveEachLineWithSeparator() throws IOException {
-        context.checking(new Expectations() {{
-            one(action).execute(String.format("line 1%n"));
-            one(action).execute(String.format("line 2%n"));
+            one(action).execute(TextUtil.toPlatformLineSeparators("line 1\n"));
+            one(action).execute(TextUtil.toPlatformLineSeparators("line 2\n"));
         }});
 
-        outputStream = new LineBufferingOutputStream(action, true);
-        outputStream.write(String.format("line 1%nline 2%n").getBytes());
+        outputStream.write(TextUtil.toPlatformLineSeparators("line 1\nline 2\n").getBytes());
     }
 
     @Test
     public void logsEmptyLines() throws IOException {
         context.checking(new Expectations() {{
-            one(action).execute("");
-            one(action).execute("");
+            one(action).execute(TextUtil.getPlatformLineSeparator());
+            one(action).execute(TextUtil.getPlatformLineSeparator());
         }});
 
-        outputStream.write(String.format("%n%n").getBytes());
+        outputStream.write(TextUtil.toPlatformLineSeparators("\n\n").getBytes());
     }
 
     @Test
     public void handlesSingleCharacterLineSeparator() throws IOException {
         context.checking(new Expectations() {{
-            one(action).execute("line 1");
-            one(action).execute("line 2");
+            one(action).execute("line 1-");
+            one(action).execute("line 2-");
         }});
 
         System.setProperty("line.separator", "-");
-        outputStream = new LineBufferingOutputStream(action, false, 8);
+        outputStream = new LineBufferingOutputStream(action, 8);
 
         outputStream.write(String.format("line 1-line 2-").getBytes());
     }
@@ -91,12 +80,12 @@ public class LineBufferingOutputStreamTest {
     @Test
     public void handlesMultiCharacterLineSeparator() throws IOException {
         context.checking(new Expectations() {{
-            one(action).execute("line 1");
-            one(action).execute("line 2");
+            one(action).execute("line 1----");
+            one(action).execute("line 2----");
         }});
 
         System.setProperty("line.separator", "----");
-        outputStream = new LineBufferingOutputStream(action, false, 8);
+        outputStream = new LineBufferingOutputStream(action, 8);
 
         outputStream.write(String.format("line 1----line 2----").getBytes());
     }
@@ -104,10 +93,10 @@ public class LineBufferingOutputStreamTest {
     @Test
     public void logsLineWhichIsLongerThanInitialBufferLength() throws IOException {
         context.checking(new Expectations() {{
-            one(action).execute("a line longer than 8 bytes long");
+            one(action).execute(TextUtil.toPlatformLineSeparators("a line longer than 8 bytes long\n"));
             one(action).execute("line 2");
         }});
-        outputStream.write(String.format("a line longer than 8 bytes long%n").getBytes());
+        outputStream.write(TextUtil.toPlatformLineSeparators("a line longer than 8 bytes long\n").getBytes());
         outputStream.write("line 2".getBytes());
         outputStream.close();
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
index 647537a..5222980 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
@@ -25,7 +25,7 @@ class LinePerThreadBufferingOutputStreamTest extends MultithreadedTestCase {
     @Test
     public void interleavesLinesFromEachThread() {
         List<String> output = [].asSynchronized()
-        Action<String> action = { line -> output << line } as Action
+        Action<String> action = { String line -> output << line.replace(TextUtil.platformLineSeparator, "<EOL>") } as Action
         LinePerThreadBufferingOutputStream outstr = new LinePerThreadBufferingOutputStream(action)
         10.times {
             start {
@@ -39,6 +39,6 @@ class LinePerThreadBufferingOutputStreamTest extends MultithreadedTestCase {
         waitForAll()
 
         assertThat(output.size(), equalTo(1000))
-        assertThat(output.findAll({!it.matches('write \\d+')}), equalTo([]))
+        assertThat(output.findAll({!it.matches('write \\d+<EOL>')}), equalTo([]))
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.groovy
new file mode 100644
index 0000000..96bc172
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/VersionNumberTest.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.util
+
+import spock.lang.*
+
+class VersionNumberTest extends Specification {
+    def "parsing"() {
+        expect:
+        VersionNumber.parse("1") == new VersionNumber(1, 0, 0, null)
+        VersionNumber.parse("1.0") == new VersionNumber(1, 0, 0, null)
+        VersionNumber.parse("1.0.0") == new VersionNumber(1, 0, 0, null)
+
+        VersionNumber.parse("1.2") == new VersionNumber(1, 2, 0, null)
+        VersionNumber.parse("1.2.3") == new VersionNumber(1, 2, 3, null)
+
+        VersionNumber.parse("1-rc1-SNAPSHOT") == new VersionNumber(1, 0, 0, "rc1-SNAPSHOT")
+        VersionNumber.parse("1.2-rc1-SNAPSHOT") == new VersionNumber(1, 2, 0, "rc1-SNAPSHOT")
+        VersionNumber.parse("1.2.3-rc1-SNAPSHOT") == new VersionNumber(1, 2, 3, "rc1-SNAPSHOT")
+
+        VersionNumber.parse("1.rc1-SNAPSHOT") == new VersionNumber(1, 0, 0, "rc1-SNAPSHOT")
+        VersionNumber.parse("1.2.rc1-SNAPSHOT") == new VersionNumber(1, 2, 0, "rc1-SNAPSHOT")
+        VersionNumber.parse("1.2.3.rc1-SNAPSHOT") == new VersionNumber(1, 2, 3, "rc1-SNAPSHOT")
+
+        VersionNumber.parse("11.22.33.44") == new VersionNumber(11, 22, 33, "44")
+        VersionNumber.parse("11.44") == new VersionNumber(11, 44, 0, null)
+        VersionNumber.parse("11.fortyfour") == new VersionNumber(11, 0, 0, "fortyfour")
+    }
+
+    def "unparseable version number is represented as UNKNOWN (0.0.0)"() {
+        expect:
+        VersionNumber.parse(null) == VersionNumber.UNKNOWN
+        VersionNumber.parse("") == VersionNumber.UNKNOWN
+        VersionNumber.parse("foo") == VersionNumber.UNKNOWN
+        VersionNumber.parse("1.") == VersionNumber.UNKNOWN
+        VersionNumber.parse("1.2.3-") == VersionNumber.UNKNOWN
+    }
+
+    def "accessors"() {
+        when:
+        def version = new VersionNumber(1, 2, 3, "foo")
+
+        then:
+        version.major == 1
+        version.minor == 2
+        version.micro == 3
+        version.qualifier == "foo"
+    }
+
+    def "string representation"() {
+        expect:
+        new VersionNumber(1, 0, 0, null).toString() == "1.0.0"
+        new VersionNumber(1, 2, 3, "foo").toString() == "1.2.3-foo"
+    }
+
+    def "equality"() {
+        expect:
+        new VersionNumber(1, 1, 1, null) == new VersionNumber(1, 1, 1, null)
+        new VersionNumber(2, 1, 1, null) != new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 2, 1, null) != new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 2, null) != new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 1, "foo") != new VersionNumber(1, 1, 1, null)
+    }
+
+    def "comparison"() {
+        expect:
+        (new VersionNumber(1, 1, 1, null) <=> new VersionNumber(1, 1, 1, null)) == 0
+
+        new VersionNumber(2, 1, 1, null) > new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 2, 1, null) > new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 2, null) > new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 1, "foo") > new VersionNumber(1, 1, 1, null)
+        new VersionNumber(1, 1, 1, "b") > new VersionNumber(1, 1, 1, "a")
+
+        new VersionNumber(1, 1, 1, null) < new VersionNumber(2, 1, 1, null)
+        new VersionNumber(1, 1, 1, null) < new VersionNumber(1, 2, 1, null)
+        new VersionNumber(1, 1, 1, null) < new VersionNumber(1, 1, 2, null)
+        new VersionNumber(1, 1, 1, null) < new VersionNumber(1, 1, 1, "foo")
+        new VersionNumber(1, 1, 1, "a") < new VersionNumber(1, 1, 1, "b")
+    }
+}
+
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/TestFiles.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/TestFiles.java
new file mode 100644
index 0000000..7517678
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/internal/file/TestFiles.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.file;
+
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+
+import java.io.File;
+
+public class TestFiles {
+    /**
+     * Returns a resolver with no base directory.
+     */
+    public static FileResolver resolver() {
+        return new IdentityFileResolver();
+    }
+
+    /**
+     * Returns a resolver with the given base directory.
+     */
+    public static FileResolver resolver(File baseDir) {
+        return new BaseDirFileResolver(FileSystems.getDefault(), baseDir);
+    }
+}
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
index fe49c7d..a82b1ca 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
@@ -14,31 +14,33 @@
  * limitations under the License.
  */
 
-package org.gradle.api.tasks;
+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.Actions
 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.internal.reflect.DirectInstantiator
 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 java.util.concurrent.atomic.AtomicBoolean
+
 import static org.junit.Assert.assertFalse
 
 /**
@@ -64,7 +66,7 @@ public abstract class AbstractSpockTaskTest extends Specification {
     }
 
     public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
-        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
+        Task task = TASK_FACTORY.createChild(project, new DirectInstantiator()).createTask(
                 GUtil.map(Task.TASK_TYPE, type,
                         Task.TASK_NAME, name))
         assert type.isAssignableFrom(task.getClass())
@@ -135,8 +137,8 @@ public abstract class AbstractSpockTaskTest extends Specification {
 
     def testDeleteAllActions() {
         when:
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
+        Action action1 = Actions.doNothing();
+        Action action2 = Actions.doNothing();
         getTask().doLast(action1);
         getTask().doLast(action2);
 
@@ -298,12 +300,4 @@ public abstract class AbstractSpockTaskTest extends Specification {
         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
index 2625127..7f6db32 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
@@ -22,16 +22,21 @@ 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.Actions;
 import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.DependencyInjectingInstantiator;
 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.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.reflect.ObjectInstantiationException;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.util.*;
 import org.jmock.Expectations;
 import org.jmock.lib.legacy.ClassImposteriser;
@@ -43,8 +48,7 @@ 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.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 /**
@@ -55,17 +59,17 @@ public abstract class AbstractTaskTest {
     @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();
-    }
+    protected DefaultServiceRegistry serviceRegistry = new DefaultServiceRegistry();
+
+    protected Instantiator instantiator = new DependencyInjectingInstantiator(serviceRegistry);
+
+    private AbstractProject project = HelperUtil.createRootProject();
+
+    private final AnnotationProcessingTaskFactory rootFactory = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
 
     public abstract AbstractTask getTask();
 
@@ -73,18 +77,23 @@ public abstract class AbstractTaskTest {
         return createTask(type, project, TEST_TASK_NAME);
     }
 
-    public Task createTask(Project project, String name) {
+    public Task createTask(ProjectInternal 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));
+    public <T extends AbstractTask> T createTask(Class<T> type, ProjectInternal project, String name) {
+        DefaultServiceRegistry registry = new DefaultServiceRegistry();
+        registry.add(Instantiator.class, new DirectInstantiator());
+        Task task = rootFactory.createChild(project, instantiator).createTask(GUtil.map(Task.TASK_TYPE, type, Task.TASK_NAME, name));
         assertTrue(type.isAssignableFrom(task.getClass()));
         return type.cast(task);
     }
 
+    @Before
+    public final void setupRegistry() {
+        serviceRegistry.add(Instantiator.class, instantiator);
+    }
+
     @Test
     public void testTask() {
         assertTrue(getTask().isEnabled());
@@ -134,8 +143,8 @@ public abstract class AbstractTaskTest {
 
     @Test
     public void testDeleteAllActions() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
+        Action<? super Task> action1 = Actions.<Task>doNothing();
+        Action<? super Task> action2 = Actions.<Task>doNothing();
         getTask().doLast(action1);
         getTask().doLast(action2);
         assertSame(getTask(), getTask().deleteAllActions());
@@ -280,23 +289,17 @@ public abstract class AbstractTaskTest {
         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");
+            instantiator.newInstance(getTask().getClass());
+            throw new RuntimeException("Direct instantiation of " + getTask().getClass() + " should have produced an exception");
+        } catch (ObjectInstantiationException e) {
+            // compared to direct instantiation, instantiator (which we use to get any ctor args injected) wraps TaskInstantiationException, so unwrap
+            Throwable cause = e.getCause();
+            assertEquals(TaskInstantiationException.class, cause.getClass());
+            assertThat(cause.getMessage(), containsString("has been instantiated directly which is not supported"));
         }
     }
 }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy
index 7d5e399..125fd31 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy
@@ -16,9 +16,6 @@
 
 package org.gradle.cache.internal
 
-import org.gradle.internal.nativeplatform.services.NativeServices
-import org.gradle.internal.nativeplatform.ProcessEnvironment
-
 abstract class DefaultFileLockManagerTestHelper {
 
     private static class AnException extends RuntimeException {}
@@ -46,7 +43,15 @@ abstract class DefaultFileLockManagerTestHelper {
     }
 
     static DefaultFileLockManager createDefaultFileLockManager() {
-        new DefaultFileLockManager(new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment)))
+        new DefaultFileLockManager(new ProcessMetaDataProvider() {
+            String getProcessIdentifier() {
+                return "pid"
+            }
+
+            String getProcessDisplayName() {
+                return "process"
+            }
+        })
     }
     
     static FileLock createDefaultFileLock(File file, FileLockManager.LockMode mode = FileLockManager.LockMode.Exclusive, DefaultFileLockManager manager = createDefaultFileLockManager()) {
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
index d6b416d..2c1f9c3 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
@@ -15,8 +15,6 @@
  */
 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
@@ -24,6 +22,7 @@ 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.internal.reflect.DirectInstantiator
 import org.gradle.testfixtures.ProjectBuilder
 import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
 import org.apache.ivy.core.module.id.ArtifactId
@@ -50,7 +49,6 @@ import org.gradle.groovy.scripts.DefaultScript
 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))
 
@@ -63,7 +61,7 @@ class HelperUtil {
      }
 
      static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
-         return TASK_FACTORY.createTask(project, [name: name, type: type])
+         return TASK_FACTORY.createChild(project, new DirectInstantiator()).createTask([name: name, type: type])
      }
 
      static DefaultProject createRootProject() {
@@ -86,12 +84,6 @@ class HelperUtil {
                  .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,
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
index 7001422..ea4f30b 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
@@ -91,6 +91,19 @@ public class Matchers {
     }
 
     @Factory
+    public static <T extends CharSequence> Matcher<T> containsText(final String pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return Pattern.compile(pattern).matcher((CharSequence) o).find();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that contains text ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
     public static <T> Matcher<T> strictlyEqual(final T other) {
         return new BaseMatcher<T>() {
             public boolean matches(Object o) {
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MockExecutor.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MockExecutor.java
new file mode 100644
index 0000000..f976e8b
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MockExecutor.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.util;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+public class MockExecutor implements Executor {
+    final List<Runnable> actions = new CopyOnWriteArrayList<Runnable>();
+
+    public void execute(Runnable command) {
+        actions.add(command);
+    }
+
+    public void runNow() {
+        while (!actions.isEmpty()) {
+            actions.remove(0).run();
+        }
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
index 6483e6a..dfbe3e8 100755
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
@@ -125,7 +125,7 @@ public class MultithreadedTestCase {
             throw new RuntimeException(String.format(
                     "Action did not block for expected time. Expected ~ %d ms, was %d ms.", expected, actual));
         }
-        if (actual > expected + 500) {
+        if (actual > expected + 1200) {
             throw new RuntimeException(String.format(
                     "Action did not complete within expected time. Expected ~ %d ms, was %d ms.", expected, actual));
         }
@@ -342,7 +342,7 @@ public class MultithreadedTestCase {
         start(new Runnable() {
             public void run() {
                 try {
-                    Thread.sleep(200L);
+                    Thread.sleep(500L);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
diff --git a/subprojects/cpp/cpp.gradle b/subprojects/cpp/cpp.gradle
index b2b4296..0bfc388 100644
--- a/subprojects/cpp/cpp.gradle
+++ b/subprojects/cpp/cpp.gradle
@@ -22,4 +22,11 @@ dependencies {
     integTestRuntime project(":maven")
 }
 
+
+integTestTasks.all {
+    if (isWindows && systemProperties['org.gradle.integtest.executer'] == "embedded") {
+        systemProperties['org.gradle.integtest.executer'] =  "forking"
+    }
+}
+
 useTestFixtures()
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
index bc7e416..ee6733d 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
@@ -40,7 +40,7 @@ public class CppIntegrationTestRunner extends AbstractMultiTestRunner {
     }
 
     private static class CompilerExecution extends Execution {
-        private static final ProcessEnvironment PROCESS_ENVIRONMENT = new NativeServices().get(ProcessEnvironment.class);
+        private static final ProcessEnvironment PROCESS_ENVIRONMENT = NativeServices.getInstance().get(ProcessEnvironment.class);
         private final AvailableCompilers.CompilerCandidate compiler;
         private String originalPath;
         private final String pathVarName;
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
index e87815c..2de4f0a 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
@@ -16,10 +16,11 @@
 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
+import org.junit.Rule
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class CppSamplesIntegrationTest extends AbstractBinariesIntegrationSpec {
     @Rule public final Sample exewithlib = new Sample('cpp/exewithlib')
@@ -45,18 +46,23 @@ class CppSamplesIntegrationTest extends AbstractBinariesIntegrationSpec {
     // Does not work on windows, due to GRADLE-2118
     @Requires(TestPrecondition.NOT_WINDOWS)
     def "dependencies"() {
-        given:
+        when:
         sample dependencies
-        
+        run ":lib:uploadArchives"
+
+        then:
+        sharedLibrary("cpp/dependencies/lib/build/binaries/lib").isFile()
+        file("cpp/dependencies/lib/build/repo/some-org/some-lib/1.0/some-lib-1.0-so.so").isFile()
+
         when:
-        run ":lib:uploadArchives", ":exe:uploadArchives"
+        sample dependencies
+        run ":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()
     }
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
index 6cc5aa7..62c0378 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
@@ -36,24 +36,24 @@ public class BinariesPlugin implements Plugin<ProjectInternal> {
         project.getPlugins().apply(BasePlugin.class);
 
         Instantiator instantiator = project.getServices().get(Instantiator.class);
-        project.getExtensions().add("compilers", instantiator.newInstance(
+        project.getExtensions().create("compilers",
                 DefaultCompilerRegistry.class,
                 instantiator
-        ));
+        );
         DefaultCompilerRegistry registry = project.getExtensions().getByType(DefaultCompilerRegistry.class);
 
-        project.getExtensions().add("executables", instantiator.newInstance(
+        project.getExtensions().create("executables",
                 FactoryNamedDomainObjectContainer.class,
                 Executable.class,
                 instantiator,
                 new ReflectiveNamedDomainObjectFactory<Executable>(DefaultExecutable.class, project, registry)
-        ));
-        project.getExtensions().add("libraries", instantiator.newInstance(
+        );
+        project.getExtensions().create("libraries",
                 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/internal/CompileTaskAware.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java
index b09a809..41760f1 100644
--- 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
@@ -16,8 +16,8 @@
 
 package org.gradle.plugins.binaries.model.internal;
 
-import org.gradle.plugins.binaries.tasks.Compile;
+import org.gradle.plugins.cpp.CppCompile;
 
 public interface CompileTaskAware {
-    void configure(Compile compileTask);
+    void configure(CppCompile compileTask);
 }
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
deleted file mode 100644
index 8c5ea2d..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/Compile.groovy
+++ /dev/null
@@ -1,32 +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.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/cpp/CppCompile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppCompile.groovy
new file mode 100644
index 0000000..b280684
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppCompile.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.api.DefaultTask
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.TaskAction
+import org.gradle.plugins.binaries.model.CompileSpec
+
+class CppCompile extends DefaultTask {
+    CompileSpec spec
+    Compiler compiler
+
+    @TaskAction
+    void compile() {
+        def result = compiler.execute(spec)
+        didWork = result.didWork
+    }
+}
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
index 9aa3b4a..d5d4862 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
@@ -24,7 +24,6 @@ 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
@@ -91,7 +90,7 @@ exec "\$APP_BASE_NAME/lib/${executable.spec.outputFile.name}" \"\$@\"
     def configureBinary(ProjectInternal project, Binary binary) {
         def baseName = GUtil.toCamelCase(binary.name).capitalize()
 
-        def task = project.task("compile${baseName}", type: Compile) {
+        def task = project.task("compile${baseName}", type: CppCompile) {
             description = "Compiles and links $binary"
             group = BasePlugin.BUILD_GROUP
         }
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
index df33e05..ccb132d 100755
--- 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
@@ -24,20 +24,20 @@ 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
+import org.gradle.plugins.cpp.CppCompile
 
 class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAware, CppCompileSpec {
     Binary binary
 
-    Compile task
+    private CppCompile task
     List<Closure> settings = []
 
     String outputFileName
@@ -58,7 +58,7 @@ class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAwa
         source = project.files()
     }
 
-    void configure(Compile task) {
+    void configure(CppCompile task) {
         this.task = task
         task.spec = this
         task.compiler = compiler
@@ -98,24 +98,6 @@ class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAwa
      * @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
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
index 1b03333..d7c1f32 100644
--- 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
@@ -35,7 +35,6 @@ 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;
@@ -45,7 +44,6 @@ public class DefaultCppSourceSet implements CppSourceSet {
 
     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());
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
index d4dc235..7c49d10 100755
--- 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
@@ -34,6 +34,11 @@ import org.gradle.internal.os.OperatingSystem
 class MicrosoftVisualCppPlugin implements Plugin<ProjectInternal> {
     void apply(ProjectInternal project) {
         project.plugins.apply(BinariesPlugin)
+
+        if (!OperatingSystem.current().windows) {
+            return
+        }
+
         project.extensions.getByType(CompilerRegistry).add(new VisualCppCompilerAdapter(
                 OperatingSystem.current(),
                 new Factory<ExecAction>() {
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
index 1bc819a..c2282e7 100644
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
@@ -20,7 +20,6 @@ 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
@@ -40,7 +39,8 @@ class CppPluginTest extends Specification {
         project.libraries instanceof NamedDomainObjectContainer
     }
 
-    def "compiler adapters are available"() {
+    @Requires(TestPrecondition.WINDOWS)
+    def "gcc and visual cpp adapters are available on windows"() {
         given:
         project.plugins.apply(CppPlugin)
 
@@ -49,6 +49,16 @@ class CppPluginTest extends Specification {
         project.compilers.searchOrder.collect { it.name } == ['visualCpp', 'gpp']
     }
 
+    @Requires(TestPrecondition.UNIX)
+    def "gcc adapter is available on unix"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        expect:
+        project.compilers.collect { it.name } == ['gpp']
+        project.compilers.searchOrder.collect { it.name } == ['gpp']
+    }
+
     def "can create some cpp source sets"() {
         given:
         project.plugins.apply(CppPlugin)
@@ -151,7 +161,7 @@ class CppPluginTest extends Specification {
 
         then:
         def compile = project.tasks['compileTest']
-        compile instanceof Compile
+        compile instanceof CppCompile
         compile.spec == project.executables.test.spec
 
         def install = project.tasks['installTest']
@@ -219,7 +229,7 @@ class CppPluginTest extends Specification {
 
         then:
         def compile = project.tasks['compileTest']
-        compile instanceof Compile
+        compile instanceof CppCompile
         compile.spec == project.libraries.test.spec
     }
 }
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
index abf084a..1ed649c 100644
--- 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
@@ -19,10 +19,10 @@ 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
+import org.gradle.plugins.cpp.CppCompile
 
 class GppCompileSpecTest extends Specification {
     final ProjectInternal project = HelperUtil.createRootProject()
@@ -31,7 +31,7 @@ class GppCompileSpecTest extends Specification {
         given:
         def binary = new DefaultBinary("binary", project, Mock(CompileSpecFactory))
         def spec = new GppCompileSpec(binary, Mock(Compiler), project)
-        def compileTask = project.tasks.add("compile", Compile)
+        def compileTask = project.tasks.add("compile", CppCompile)
         spec.configure(compileTask)
 
         expect:
diff --git a/subprojects/diagnostics/diagnostics.gradle b/subprojects/diagnostics/diagnostics.gradle
new file mode 100644
index 0000000..c430922
--- /dev/null
+++ b/subprojects/diagnostics/diagnostics.gradle
@@ -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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+    compile project(':core')
+    compile project(':plugins')
+    testCompile project(':coreImpl')
+}
+
+useTestFixtures()
+useTestFixtures(project: ':coreImpl')
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/plugins/ProjectReportsPluginIntegrationTest.java b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/plugins/ProjectReportsPluginIntegrationTest.java
new file mode 100644
index 0000000..69e91e7
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/plugins/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.api.plugins;
+
+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/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..03d02e6
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class DependencyInsightReportTaskIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        distribution.requireOwnUserHomeDir()
+    }
+
+    def "shows basic single tree with repeated dependency"() {
+        given:
+        mavenRepo.module("org", "leaf1").publish()
+        mavenRepo.module("org", "leaf2").publish()
+
+        mavenRepo.module("org", "middle").dependsOn("leaf1", "leaf2").publish()
+
+        mavenRepo.module("org", "top").dependsOn("middle", "leaf2").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf2' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf2:1.0
++--- org:top:1.0
+|    \\--- conf
+\\--- org:middle:1.0
+     \\--- org:top:1.0 (*)
+
+(*) - dependencies omitted (listed previously)
+"""))
+    }
+
+    def "basic dependency insight with conflicting versions"() {
+        given:
+        mavenRepo.module("org", "leaf1").publish()
+        mavenRepo.module("org", "leaf2").publish()
+        mavenRepo.module("org", "leaf2", 1.5).publish()
+        mavenRepo.module("org", "leaf2", 2.5).publish()
+        mavenRepo.module("org", "leaf3").publish()
+        mavenRepo.module("org", "leaf4").publish()
+
+        mavenRepo.module("org", "middle1").dependsOn('leaf1', 'leaf2').publish()
+        mavenRepo.module("org", "middle2").dependsOn('leaf3', 'leaf4').publish()
+        mavenRepo.module("org", "middle3").dependsOn('leaf2').publish()
+
+        mavenRepo.module("org", "toplevel").dependsOn("middle1", "middle2").publish()
+
+        mavenRepo.module("org", "toplevel2").dependsOn("org", "leaf2", "1.5").publish()
+        mavenRepo.module("org", "toplevel3").dependsOn("org", "leaf2", "2.5").publish()
+
+        mavenRepo.module("org", "toplevel4").dependsOn("middle3").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:toplevel:1.0', 'org:toplevel2:1.0', 'org:toplevel3:1.0', 'org:toplevel4:1.0'
+            }
+        """
+
+        when:
+        run "dependencyInsight", "--dependency", "leaf2", "--configuration", "conf"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf2:2.5 (conflict resolution)
+\\--- org:toplevel3:1.0
+     \\--- conf
+
+org:leaf2:1.0 -> 2.5
++--- org:middle1:1.0
+|    \\--- org:toplevel:1.0
+|         \\--- conf
+\\--- org:middle3:1.0
+     \\--- org:toplevel4:1.0
+          \\--- conf
+
+org:leaf2:1.5 -> 2.5
+\\--- org:toplevel2:1.0
+     \\--- conf
+"""))
+    }
+
+    def "shows forced version"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            configurations.conf.resolutionStrategy.force 'org:leaf:1.0'
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.name == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.0 (forced)
+\\--- org:foo:1.0
+     \\--- conf
+
+org:leaf:2.0 -> 1.0
+\\--- org:bar:1.0
+     \\--- conf
+"""))
+    }
+
+    def "forced version matches the conflict resolution"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            configurations.conf.resolutionStrategy.force 'org:leaf:2.0'
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.name == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:2.0 (forced)
+\\--- org:bar:1.0
+     \\--- conf
+
+org:leaf:1.0 -> 2.0
+\\--- org:foo:1.0
+     \\--- conf
+"""))
+    }
+
+    def "forced version does not match anything in the graph"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+        mavenRepo.module("org", "leaf", 1.5).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            configurations.conf.resolutionStrategy.force 'org:leaf:1.5'
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.name == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.5 (forced)
+
+org:leaf:1.0 -> 1.5
+\\--- org:foo:1.0
+     \\--- conf
+
+org:leaf:2.0 -> 1.5
+\\--- org:bar:1.0
+     \\--- conf
+"""))
+    }
+
+    def "forced version at dependency level"() {
+        given:
+        mavenRepo.module("org", "leaf", 1.0).publish()
+        mavenRepo.module("org", "leaf", 2.0).publish()
+
+        mavenRepo.module("org", "foo", 1.0).dependsOn('org', 'leaf', '1.0').publish()
+        mavenRepo.module("org", "bar", 1.0).dependsOn('org', 'leaf', '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:foo:1.0', 'org:bar:1.0'
+                conf('org:leaf:1.0') {
+                  force = true
+                }
+            }
+            task insight(type: DependencyInsightReportTask) {
+                configuration = configurations.conf
+                setDependencySpec { it.requested.name == 'leaf' }
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf:1.0 (forced)
++--- conf
+\\--- org:foo:1.0
+     \\--- conf
+
+org:leaf:2.0 -> 1.0
+\\--- org:bar:1.0
+     \\--- conf
+"""))
+    }
+
+    def "shows decent failure when inputs missing"() {
+        given:
+        file("build.gradle") << """
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf2' }
+            }
+        """
+
+        when:
+        def failure = runAndFail("insight")
+
+        then:
+        failure.assertHasCause("Dependency insight report cannot be generated because the input configuration was not specified.")
+    }
+
+    def "informs that there are no dependencies"() {
+        given:
+        file("build.gradle") << """
+            configurations {
+                conf
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'whatever' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains("No resolved dependencies matching given input were found")
+    }
+
+    def "informs that nothing matches the input dependency"() {
+        given:
+        mavenRepo.module("org", "top").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'foo.unknown' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains("No resolved dependencies matching given input were found")
+    }
+
+    def "deals with unresolved dependencies"() {
+        given:
+        mavenRepo.module("org", "top").dependsOn("middle").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:top:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'middle' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains("No resolved dependencies matching given input were found")
+    }
+
+    def "deals with dependency cycles"() {
+        given:
+        mavenRepo.module("org", "leaf1").dependsOn("leaf2").publish()
+        mavenRepo.module("org", "leaf2").dependsOn("leaf1").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:leaf1:1.0'
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf2' }
+                configuration = configurations.conf
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf2:1.0
+\\--- org:leaf1:1.0
+     +--- conf
+     \\--- org:leaf2:1.0 (*)
+"""))
+    }
+
+    def "deals with dependency cycle to root"() {
+        given:
+        file("settings.gradle") << "include 'impl'; rootProject.name='root'"
+
+        file("build.gradle") << """
+            allprojects {
+                apply plugin: 'java'
+                group = 'org.foo'
+                version = '1.0'
+            }
+            archivesBaseName = 'root'
+            dependencies {
+                compile project(":impl")
+            }
+            project(":impl") {
+                dependencies {
+                    compile project(":")
+                }
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { true }
+                configuration = configurations.compile
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org.foo:root:1.0
+\\--- org.foo:impl:1.0
+     \\--- org.foo:root:1.0 (*)"""))
+    }
+
+    def "shows project dependencies"() {
+        given:
+        mavenRepo.module("org", "leaf1").dependsOn("leaf2").publish()
+        mavenRepo.module("org", "leaf2").dependsOn("leaf3").publish()
+        mavenRepo.module("org", "leaf3").publish()
+
+        file("settings.gradle") << "include 'impl'; rootProject.name='root'"
+
+        file("build.gradle") << """
+            allprojects {
+                apply plugin: 'java'
+                group = 'org.foo'
+                version = '1.0-SNAPSHOT'
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+            dependencies {
+                compile project(':impl')
+            }
+            project(':impl') {
+                dependencies {
+                    compile 'org:leaf1:1.0'
+                }
+            }
+            task insight(type: DependencyInsightReportTask) {
+                setDependencySpec { it.requested.name == 'leaf2' }
+                configuration = configurations.compile
+            }
+        """
+
+        when:
+        run "insight"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+org:leaf2:1.0
+\\--- org:leaf1:1.0
+     \\--- org.foo:impl:1.0-SNAPSHOT
+          \\--- compile
+"""))
+    }
+}
diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..a24ec66
--- /dev/null
+++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        distribution.requireOwnUserHomeDir()
+    }
+
+    def "omits repeated dependencies in case of 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)"
+    }
+
+    def "renders even if resolution fails"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).dependsOn("i dont exist").publish()
+        mavenRepo.module("foo", "baz", 1.0).dependsOn("foo:bar:1.0").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { foo }
+            dependencies {
+              foo 'i:dont:exist'
+              foo 'foo:baz:1.0'
+            }
+        """
+
+        when:
+        executer.allowExtraLogging = false
+        runAndFail "dependencies"
+
+        then:
+        errorOutput.contains('Could not resolve all dependencies')
+        output.contains(toPlatformLineSeparators("""
+foo
+\\--- foo:baz:1.0
+"""
+        ))
+    }
+
+    def "renders dependencies even if the configuration was already resolved"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).publish()
+        mavenRepo.module("foo", "bar", 2.0).publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+            configurations { foo }
+            dependencies {
+                foo 'foo:bar:1.0'
+                foo 'foo:bar:2.0'
+            }
+
+            task resolveConf << {
+                configurations.foo.each { println it }
+            }
+        """
+
+        when:
+        run "resolveConf", "dependencies"
+
+        then:
+        output.contains "foo:bar:1.0 -> 2.0"
+    }
+
+    def "renders selected versions in case of a conflict"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).publish()
+        mavenRepo.module("foo", "bar", 2.0).publish()
+        mavenRepo.module("foo", "bar", 3.0).dependsOn('foo', 'baz', '5.0').publish()
+
+
+        mavenRepo.module("foo", "baz", 5.0).publish()
+
+        file("settings.gradle") << """include 'a', 'b', 'c', 'd', 'e'
+rootProject.name = 'root'
+"""
+
+        file("build.gradle") << """
+            allprojects {
+                apply plugin: 'java'
+                version = '1.0'
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+
+            project(":a") {
+               dependencies {
+                    compile 'foo:bar:1.0'
+                }
+            }
+
+            project(":b") {
+               dependencies {
+                    compile 'foo:bar:0.5.dont.exist'
+                }
+            }
+
+            project(":c") {
+               dependencies {
+                    compile 'foo:bar:3.0'
+               }
+            }
+
+            project(":d") {
+               dependencies {
+                    compile 'foo:bar:2.0'
+               }
+            }
+
+            project(":e") {
+               dependencies {
+                    compile 'foo:bar:3.0'
+               }
+            }
+
+            dependencies {
+                compile project(":a"), project(":b"), project(":c"), project(":d"), project(":e")
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains 'compile - Classpath for compiling the main sources.'
+
+        output.contains(toPlatformLineSeparators("""
++--- root:a:1.0
+|    \\--- foo:bar:1.0 -> 3.0
+|         \\--- foo:baz:5.0
++--- root:b:1.0
+|    \\--- foo:bar:0.5.dont.exist -> 3.0 (*)
++--- root:c:1.0
+|    \\--- foo:bar:3.0 (*)
++--- root:d:1.0
+|    \\--- foo:bar:2.0 -> 3.0 (*)
+\\--- root:e:1.0
+     \\--- foo:bar:3.0 (*)
+"""))
+    }
+
+    def "renders the dependency tree"() {
+        given:
+        mavenRepo.module("org", "leaf1").publish()
+        mavenRepo.module("org", "leaf2").publish()
+        mavenRepo.module("org", "leaf3").publish()
+        mavenRepo.module("org", "leaf4").publish()
+
+        mavenRepo.module("org", "middle1").dependsOn('leaf1', 'leaf2').publish()
+        mavenRepo.module("org", "middle2").dependsOn('leaf3', 'leaf4').publish()
+
+        mavenRepo.module("org", "toplevel").dependsOn("middle1", "middle2").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:toplevel:1.0'
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+\\--- org:toplevel:1.0
+     +--- org:middle1:1.0
+     |    +--- org:leaf1:1.0
+     |    \\--- org:leaf2:1.0
+     \\--- org:middle2:1.0
+          +--- org:leaf3:1.0
+          \\--- org:leaf4:1.0
+"""))
+    }
+
+    def "shows selected versions in case of a multi-phase conflict"() {
+        given:
+        mavenRepo.module("foo", "foo", 1.0).publish()
+        mavenRepo.module("foo", "foo", 2.0).publish()
+        mavenRepo.module("foo", "foo", 3.0).publish()
+        mavenRepo.module("foo", "foo", 4.0).publish()
+
+        mavenRepo.module("bar", "bar", 5.0).dependsOn("foo", "foo", "4.0").publish()
+        mavenRepo.module("bar", "bar", 6.0).dependsOn("foo", "foo", "3.0").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'bar:bar:5.0'
+                conf 'bar:bar:6.0'
+                conf 'foo:foo:1.0'
+                conf 'foo:foo:2.0'
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
++--- bar:bar:5.0 -> 6.0
+|    \\--- foo:foo:3.0
++--- bar:bar:6.0 (*)
++--- foo:foo:1.0 -> 3.0 (*)
+\\--- foo:foo:2.0 -> 3.0 (*)
+"""))
+    }
+
+    def "deals with dynamic versions with conflicts"() {
+        given:
+        mavenRepo.module("foo", "bar", 1.0).publish()
+        mavenRepo.module("foo", "bar", 2.0).publish()
+
+        mavenRepo.module("foo", "foo", 1.0).dependsOn("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "foo", 2.0).dependsOn("foo", "bar", "1.0").publish()
+        mavenRepo.module("foo", "foo", 2.5).dependsOn("foo", "bar", "2.0").publish()
+
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'foo:foo:1+'
+                conf 'foo:foo:2+'
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
++--- foo:foo:1+ -> 2.5
+|    \\--- foo:bar:2.0
+\\--- foo:foo:2+ -> 2.5 (*)
+"""))
+    }
+
+    def "renders ivy tree with custom configurations"() {
+        given:
+        def module = ivyRepo.module("org", "child")
+        module.configurations['first'] = [extendsFrom: ['second'], transitive: true]
+        module.configurations['second'] = [extendsFrom: [], transitive: true]
+        module.publish()
+
+        module = ivyRepo.module("org", "parent").dependsOn('child')
+        module.configurations['first'] = [extendsFrom: ['second'], transitive: true]
+        module.configurations['second'] = [extendsFrom: [], transitive: true]
+        module.publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:parent:1.0'
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains "org:child:1.0"
+    }
+
+    def "renders the ivy tree with conflicts"() {
+        given:
+        ivyRepo.module("org", "leaf1").publish()
+        ivyRepo.module("org", "leaf2").publish()
+        ivyRepo.module("org", "leaf3").publish()
+        ivyRepo.module("org", "leaf4").publish()
+        ivyRepo.module("org", "leaf4", 2.0).publish()
+
+        //also asserting on correct order of transitive dependencies
+        ivyRepo.module("org", "middle1").dependsOn('leaf1', 'leaf2').publish()
+        ivyRepo.module("org", "middle2").dependsOn('leaf3', 'leaf4') publish()
+
+        ivyRepo.module("org", "toplevel").dependsOn("middle1", "middle2").publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:toplevel:1.0', 'org:leaf4:2.0'
+            }
+        """
+
+        when:
+        run ":dependencies"
+
+        then:
+        output.contains(toPlatformLineSeparators("""
++--- org:toplevel:1.0
+|    +--- org:middle1:1.0
+|    |    +--- org:leaf1:1.0
+|    |    \\--- org:leaf2:1.0
+|    \\--- org:middle2:1.0
+|         +--- org:leaf3:1.0
+|         \\--- org:leaf4:1.0 -> 2.0
+\\--- org:leaf4:2.0 (*)
+"""))
+    }
+
+    def "tells if there are no dependencies"() {
+        given:
+        buildFile << "configurations { foo }"
+
+        when:
+        run "dependencies"
+
+        then:
+        output.contains "No dependencies"
+    }
+
+    def "tells if there are no configurations"() {
+        when:
+        run "dependencies"
+
+        then:
+        output.contains "No configurations"
+    }
+
+    def "dependencies report does not run for subprojects by default"() {
+        given:
+        file("settings.gradle") << "include 'a'"
+
+        file("build.gradle") << """
+        project(":a") {
+          configurations { foo }
+          dependencies {
+            foo "i.dont.exist:foo:1.0"
+          }
+        }
+"""
+        when:
+        run "dependencies"
+
+        then:
+        noExceptionThrown()
+        //note that 'a' project dependencies are not being resolved
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/HelpTasksPlugin.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/HelpTasksPlugin.groovy
new file mode 100644
index 0000000..1ef988e
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/HelpTasksPlugin.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.configuration.Help
+import org.gradle.api.tasks.diagnostics.*
+
+import static org.gradle.configuration.ImplicitTasksConfigurer.*
+
+/**
+ * by Szczepan Faber, created at: 9/5/12
+ */
+ at Incubating
+class HelpTasksPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.implicitTasks.add(name: HELP_TASK, type: Help) {
+            description = "Displays a help message"
+            group = HELP_GROUP
+        }
+
+        project.implicitTasks.add(name: PROJECTS_TASK, type: ProjectReportTask) {
+            description = "Displays the sub-projects of $project."
+            group = HELP_GROUP
+        }
+
+        project.implicitTasks.add(name: TASKS_TASK, type: TaskReportTask) {
+            description = "Displays the tasks runnable from $project (some of the displayed tasks may belong to subprojects)."
+            group = HELP_GROUP
+        }
+
+        project.implicitTasks.add(name: PROPERTIES_TASK, type: PropertyReportTask) {
+            description = "Displays the properties of $project."
+            group = HELP_GROUP
+        }
+
+        project.implicitTasks.add(name: DEPENDENCY_INSIGHT_TASK, type: DependencyInsightReportTask) { task ->
+            description = "Displays the insight into a specific dependency in $project."
+            group = HELP_GROUP
+            project.plugins.withType(JavaPlugin) {
+                task.configuration = project.configurations.compile
+            }
+        }
+
+        project.implicitTasks.add(name: DEPENDENCIES_TASK, type: DependencyReportTask) {
+            description = "Displays all dependencies declared in $project."
+            group = HELP_GROUP
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
new file mode 100644
index 0000000..f52a767
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.util.WrapUtil
+
+public class ProjectReportsPluginConvention {
+    /**
+     * The name of the directory to generate the project reports into, relative to the project's reports dir.
+     */
+    String projectReportDirName = 'project'
+    private final Project project
+
+    def ProjectReportsPluginConvention(Project project) {
+        this.project = project;
+    }
+
+    /**
+     * Returns the directory to generate the project reports into.
+     */
+    File getProjectReportDir() {
+        project.extensions.getByType(ReportingExtension).file(projectReportDirName)
+    }
+
+    Set<Project> getProjects() {
+        WrapUtil.toSet(project)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy
new file mode 100644
index 0000000..4df41c3
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTask.groovy
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.result.DependencyResult
+import org.gradle.api.artifacts.result.ResolutionResult
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
+import org.gradle.api.internal.tasks.CommandLineOption
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.diagnostics.internal.GraphRenderer
+import org.gradle.api.tasks.diagnostics.internal.dsl.DependencyResultSpecNotationParser
+import org.gradle.api.tasks.diagnostics.internal.graph.DependencyGraphRenderer
+import org.gradle.api.tasks.diagnostics.internal.graph.NodeRenderer
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.api.tasks.diagnostics.internal.insight.DependencyInsightReporter
+import org.gradle.logging.StyledTextOutput
+import org.gradle.logging.StyledTextOutputFactory
+
+import javax.inject.Inject
+
+import static org.gradle.logging.StyledTextOutput.Style.Info
+
+/**
+ * Generates a report that attempts to answer questions like:
+ * <ul>
+ *  <li>Why is this dependency in the dependency graph?</li>
+ *  <li>Exactly which dependencies are pulling this dependency into the graph?</li>
+ *  <li>What is the actual version (i.e. *selected* version) of the dependency that will be used? Is it the same as what was *requested*?</li>
+ *  <li>Why is the *selected* version of a dependency different to the *requested*?</li>
+ * </ul>
+ *
+ * Use this task to get insight into a particular dependency (or dependencies)
+ * and find out what exactly happens during dependency resolution and conflict resolution.
+ * If the dependency version was forced or selected by the conflict resolution
+ * this information will be available in the report.
+ * <p>
+ * While the regular dependencies report ({@link DependencyReportTask}) shows the path from the top level dependencies down through the transitive dependencies,
+ * the dependency insight report shows the path from a particular dependency to the dependencies that pulled it in.
+ * That is, it is an inverted view of the regular dependencies report.
+ * <p>
+ * The task requires setting the dependency spec and the configuration.
+ * For more information on how to configure those please refer to docs for
+ * {@link DependencyInsightReportTask#setDependencySpec(Object)} and
+ * {@link DependencyInsightReportTask#setConfiguration(String)}.
+ * <p>
+ * The task can also be configured from the command line.
+ * For more information please refer to {@link DependencyInsightReportTask#setDependencySpec(Object)}
+ * and {@link DependencyInsightReportTask#setConfiguration(String)}
+ */
+ at Incubating
+public class DependencyInsightReportTask extends DefaultTask {
+
+    /**
+     * Configuration to look the dependency in
+     */
+    Configuration configuration;
+
+    /**
+     * Selects the dependency (or dependencies if multiple matches found) to show the report for.
+     */
+    Spec<DependencyResult> dependencySpec;
+
+    private final StyledTextOutput output;
+    private final GraphRenderer renderer;
+
+    @Inject
+    DependencyInsightReportTask(StyledTextOutputFactory outputFactory) {
+        output = outputFactory.create(getClass());
+        renderer = new GraphRenderer(output);
+    }
+
+    /**
+     * The dependency spec selects the dependency (or dependencies if multiple matches found) to show the report for.
+     * The spec receives an instance of {@link DependencyResult} as parameter.
+     *
+     * @param dependencySpec
+     */
+    public void setDependencySpec(Spec<DependencyResult> dependencySpec) {
+        this.dependencySpec = dependencySpec;
+    }
+
+    /**
+     * Configures the dependency to show the report for.
+     * Multiple notation formats are supported: Strings, instances of {@link Spec}
+     * and groovy closures. Spec and closure receive {@link DependencyResult} as parameter.
+     * Examples of String notation: 'org.slf4j:slf4j-api', 'slf4j-api', or simply: 'slf4j'.
+     * The input may potentially match multiple dependencies.
+     * See also {@link DependencyInsightReportTask#setDependencySpec(Spec)}
+     * <p>
+     * This method is exposed to the command line interface. Example usage:
+     * <pre>gradle dependencyInsight --dependency slf4j</pre>
+     *
+     * @param dependencyInsightNotation
+     */
+    @CommandLineOption(options = "dependency", description = "Shows the details of given dependency.")
+    public void setDependencySpec(Object dependencyInsightNotation) {
+        def parser = DependencyResultSpecNotationParser.create()
+        this.dependencySpec = parser.parseNotation(dependencyInsightNotation)
+    }
+
+    /**
+     * Sets the configuration to look the dependency in.
+     *
+     * @param configuration
+     */
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    /**
+     * Sets the configuration (via name) to look the dependency in.
+     * <p>
+     * This method is exposed to the command line interface. Example usage:
+     * <pre>gradle dependencyInsight --configuration runtime --dependency slf4j</pre>
+     *
+     * @param configurationName
+     */
+    @CommandLineOption(options = "configuration", description = "Looks for the dependency in given configuration.")
+    public void setConfiguration(String configurationName) {
+        this.configuration = project.configurations.getByName(configurationName)
+    }
+
+    @TaskAction
+    public void report() {
+        if (configuration == null) {
+            throw new ReportException("Dependency insight report cannot be generated because the input configuration was not specified. "
+                    + "\nIt can be specified from the command line, e.g: '$path --configuration someConf --dependency someDep'")
+        }
+        if (dependencySpec == null) {
+            throw new ReportException("Dependency insight report cannot be generated because the dependency to show was not specified."
+                    + "\nIt can be specified from the command line, e.g: '$path --dependency someDep'")
+        }
+
+        ResolutionResult result = configuration.getIncoming().getResolutionResult();
+
+        Set<DependencyResult> selectedDependencies = new LinkedHashSet<DependencyResult>()
+        result.allDependencies { DependencyResult it ->
+            //TODO SF revisit when developing unresolved dependencies story
+            if (it instanceof ResolvedDependencyResult && dependencySpec.isSatisfiedBy(it)) {
+                selectedDependencies << it
+            }
+        }
+
+        if (selectedDependencies.empty) {
+            output.println("No resolved dependencies matching given input were found in $configuration")
+            return
+        }
+
+        def sortedDeps = new DependencyInsightReporter().prepare(selectedDependencies)
+
+        def nodeRenderer = new NodeRenderer() {
+            void renderNode(StyledTextOutput output, RenderableDependency node, Set<RenderableDependency> children, boolean alreadyRendered) {
+                boolean leaf = children.empty
+                output.text(leaf? DependencyInsightReportTask.this.configuration.name : node.name);
+                if (alreadyRendered && !leaf) {
+                    output.withStyle(Info).text(" (*)")
+                }
+            }
+        }
+        def dependencyGraphRenderer = new DependencyGraphRenderer(renderer, nodeRenderer)
+
+        int i = 1
+        for (RenderableDependency dependency: sortedDeps) {
+            renderer.visit(new Action<StyledTextOutput>() {
+                public void execute(StyledTextOutput out) {
+                    out.withStyle(StyledTextOutput.Style.Identifier).text(dependency.name);
+                    if (dependency.description) {
+                        out.withStyle(StyledTextOutput.Style.Description).text(" (" + dependency.description + ")")
+                    }
+                }
+            }, true);
+            dependencyGraphRenderer.render(dependency)
+            boolean last = i++ == sortedDeps.size()
+            if (!last) {
+                output.println()
+            }
+        }
+
+        dependencyGraphRenderer.printLegend()
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
new file mode 100644
index 0000000..16d682f
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
@@ -0,0 +1,90 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
+import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
+import org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Displays the dependency tree for a project. Can be configured to output to a file, and to optionally output a
+ * graphviz compatible "dot" graph. An instance of this type is used when you execute the {@code dependencies} task from
+ * the command-line.
+ *
+ * @author Phil Messenger
+ */
+public class DependencyReportTask extends AbstractReportTask {
+
+    private DependencyReportRenderer renderer = new AsciiDependencyReportRenderer();
+
+    private Set<Configuration> configurations;
+
+    public ReportRenderer getRenderer() {
+        return renderer;
+    }
+
+    /**
+     * Set the renderer to use to build a report. If unset, AsciiGraphRenderer will be used.
+     */
+    public void setRenderer(DependencyReportRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public void generate(Project project) throws IOException {
+        SortedSet<Configuration> sortedConfigurations = new TreeSet<Configuration>(new Comparator<Configuration>() {
+            public int compare(Configuration conf1, Configuration conf2) {
+                return conf1.getName().compareTo(conf2.getName());
+            }
+        });
+        sortedConfigurations.addAll(getConfigurations(project));
+        for (Configuration configuration : sortedConfigurations) {
+            renderer.startConfiguration(configuration);
+            renderer.render(configuration);
+            renderer.completeConfiguration(configuration);
+        }
+    }
+
+    private Set<Configuration> getConfigurations(Project project) {
+        return configurations != null ? configurations : project.getConfigurations();
+    }
+
+    /**
+     * Returns the configurations to generate the report for. Default to all configurations of this task's containing
+     * project.
+     *
+     * @return the configurations.
+     */
+    public Set<Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    /**
+     * Sets the configurations to generate the report for.
+     *
+     * @param configurations The configuration. Must not be null.
+     */
+    public void setConfigurations(Set<Configuration> configurations) {
+        this.configurations = configurations;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
new file mode 100644
index 0000000..2afc254
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
@@ -0,0 +1,50 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.tasks.diagnostics.internal.PropertyReportRenderer;
+import org.gradle.api.tasks.diagnostics.internal.ReportRenderer;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Displays the properties of a project. An instance of this type is used when you execute the {@code properties} task
+ * from the command-line.
+ */
+public class PropertyReportTask extends AbstractReportTask {
+    private PropertyReportRenderer renderer = new PropertyReportRenderer();
+
+    public ReportRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(PropertyReportRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public void generate(Project project) throws IOException {
+        for (Map.Entry<String, ?> entry : new TreeMap<String, Object>(project.getProperties()).entrySet()) {
+            if (entry.getKey().equals("properties")) {
+                renderer.addProperty(entry.getKey(), "{...}");
+            } else {
+                renderer.addProperty(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.java
new file mode 100644
index 0000000..4539b34
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/ReportException.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.tasks.diagnostics;
+
+import org.gradle.api.GradleException;
+
+/**
+ * by Szczepan Faber, created at: 9/20/12
+ */
+public class ReportException extends GradleException {
+
+    public ReportException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java
new file mode 100644
index 0000000..ab54f09
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModel.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
+import org.gradle.util.Path;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+public class AggregateMultiProjectTaskReportModel implements TaskReportModel {
+    private List<TaskReportModel> projects = new ArrayList<TaskReportModel>();
+    private SetMultimap<String, TaskDetails> groups;
+    private final boolean mergeTasksWithSameName;
+
+    public AggregateMultiProjectTaskReportModel(boolean mergeTasksWithSameName) {
+        this.mergeTasksWithSameName = mergeTasksWithSameName;
+    }
+
+    public void add(TaskReportModel project) {
+        projects.add(project);
+    }
+
+    public void build() {
+        groups = TreeMultimap.create(new Comparator<String>() {
+            public int compare(String string1, String string2) {
+                return string1.compareToIgnoreCase(string2);
+            }
+        }, new Comparator<TaskDetails>() {
+            public int compare(TaskDetails task1, TaskDetails task2) {
+                return task1.getPath().compareTo(task2.getPath());
+            }
+        });
+        for (TaskReportModel project : projects) {
+            for (String group : project.getGroups()) {
+                for (final TaskDetails task : project.getTasksForGroup(group)) {
+                    groups.put(group, mergeTasksWithSameName ? new MergedTaskDetails(task) : task);
+                }
+            }
+        }
+    }
+
+    public Set<String> getGroups() {
+        return groups.keySet();
+    }
+
+    public Set<TaskDetails> getTasksForGroup(String group) {
+        return groups.get(group);
+    }
+
+    private static class MergedTaskDetails implements TaskDetails {
+        private final TaskDetails task;
+
+        public MergedTaskDetails(TaskDetails task) {
+            this.task = task;
+        }
+
+        public Path getPath() {
+            return Path.path(task.getPath().getName());
+        }
+
+        public Set<TaskDetails> getChildren() {
+            return task.getChildren();
+        }
+
+        public String getDescription() {
+            return task.getDescription();
+        }
+
+        public Set<TaskDetails> getDependencies() {
+            return task.getDependencies();
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.java
new file mode 100644
index 0000000..6f1c22d
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModel.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.tasks.diagnostics.internal;
+
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
+import org.gradle.util.GUtil;
+import org.gradle.util.Path;
+
+import java.util.Comparator;
+import java.util.Set;
+
+public class DefaultGroupTaskReportModel implements TaskReportModel {
+    public static final String OTHER_GROUP = "other";
+    private static final Comparator<String> STRING_COMPARATOR = GUtil.caseInsensitive();
+    private SetMultimap<String, TaskDetails> groups;
+
+    public void build(TaskReportModel model) {
+        Comparator<String> keyComparator = GUtil.last(GUtil.last(STRING_COMPARATOR, OTHER_GROUP), DEFAULT_GROUP);
+        Comparator<TaskDetails> taskComparator = new Comparator<TaskDetails>() {
+            public int compare(TaskDetails task1, TaskDetails task2) {
+                int diff = STRING_COMPARATOR.compare(task1.getPath().getName(), task2.getPath().getName());
+                if (diff != 0) {
+                    return diff;
+                }
+                Path parent1 = task1.getPath().getParent();
+                Path parent2 = task2.getPath().getParent();
+                if (parent1 == null && parent2 != null) {
+                    return -1;
+                }
+                if (parent1 != null && parent2 == null) {
+                    return 1;
+                }
+                if (parent1 == null) {
+                    return 0;
+                }
+                return parent1.compareTo(parent2);
+            }
+        };
+        groups = TreeMultimap.create(keyComparator, taskComparator);
+        for (String group : model.getGroups()) {
+            groups.putAll(group, model.getTasksForGroup(group));
+        }
+        String otherGroupName = findOtherGroup(groups.keySet());
+        if (otherGroupName != null && groups.keySet().contains(DEFAULT_GROUP)) {
+            groups.putAll(otherGroupName, groups.removeAll(DEFAULT_GROUP));
+        }
+        if (groups.keySet().contains(DEFAULT_GROUP) && groups.keySet().size() > 1) {
+            groups.putAll(OTHER_GROUP, groups.removeAll(DEFAULT_GROUP));
+        }
+    }
+
+    private String findOtherGroup(Set<String> groupNames) {
+        for (String groupName : groupNames) {
+            if (groupName.equalsIgnoreCase(OTHER_GROUP)) {
+                return groupName;
+            }
+        }
+        return null;
+    }
+
+    public Set<String> getGroups() {
+        return groups.keySet();
+    }
+
+    public Set<TaskDetails> getTasksForGroup(String group) {
+        return groups.get(group);
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
new file mode 100644
index 0000000..2c44eb2
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/DependencyReportRenderer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tasks.diagnostics.internal;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.IOException;
+
+/**
+ * A {@code DependencyReportRenderer} is responsible for rendering the model of a project dependency report.
+ *
+ * @author Phil Messenger
+ */
+public interface DependencyReportRenderer extends ReportRenderer {
+    /**
+     * Starts rendering the given configuration.
+     * @param configuration The configuration.
+     */
+    void startConfiguration(Configuration configuration);
+
+    /**
+     * Writes the given dependency graph for the current configuration.
+     *
+     * @param configuration The configuration.
+     */
+    void render(Configuration configuration) throws IOException;
+
+    /**
+     * Completes the rendering of the given configuration.
+     * @param configuration The configuration
+     */
+    void completeConfiguration(Configuration configuration);
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java
new file mode 100644
index 0000000..03eef17
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/GraphRenderer.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Action;
+import org.gradle.logging.StyledTextOutput;
+
+import static org.gradle.logging.StyledTextOutput.Style.Info;
+
+public class GraphRenderer {
+    private final StyledTextOutput output;
+    private StringBuilder prefix = new StringBuilder();
+    private boolean seenRootChildren;
+    private boolean lastChild = true;
+
+    public GraphRenderer(StyledTextOutput output) {
+        this.output = output;
+    }
+
+    /**
+     * Visits a node in the graph.
+     */
+    public void visit(Action<? super StyledTextOutput> node, boolean lastChild) {
+        if (seenRootChildren) {
+            output.withStyle(Info).text(prefix + (lastChild ? "\\--- " : "+--- "));
+        }
+        this.lastChild = lastChild;
+        node.execute(output);
+        output.println();
+    }
+
+    /**
+     * Starts visiting the children of the most recently visited node.
+     */
+    public void startChildren() {
+        if (seenRootChildren) {
+            prefix.append(lastChild ? "     " : "|    ");
+        }
+        seenRootChildren = true;
+    }
+
+    /**
+     * Completes visiting the children of the node which most recently started visiting children.
+     */
+    public void completeChildren() {
+        if (prefix.length() == 0) {
+            seenRootChildren = false;
+        } else {
+            prefix.setLength(prefix.length() - 5);
+        }
+    }
+
+    public StyledTextOutput getOutput() {
+        return output;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRenderer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRenderer.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRenderer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/ReportRenderer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetails.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetails.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetails.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetails.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactory.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactory.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactory.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportModel.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportModel.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportModel.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportModel.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
new file mode 100644
index 0000000..7d560d1
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
@@ -0,0 +1,157 @@
+/*
+ * 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.diagnostics.internal;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Rule;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GUtil;
+import org.gradle.util.Path;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static org.gradle.logging.StyledTextOutput.Style.*;
+
+/**
+ * <p>A {@code TaskReportRenderer} is responsible for rendering the model of a project task report.</p>
+ *
+ * @author Hans Dockter
+ */
+public class TaskReportRenderer extends TextReportRenderer {
+    private boolean currentProjectHasTasks;
+    private boolean currentProjectHasRules;
+    private boolean hasContent;
+    private boolean detail;
+
+    @Override
+    public void startProject(Project project) {
+        currentProjectHasTasks = false;
+        currentProjectHasRules = false;
+        hasContent = false;
+        detail = false;
+        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;
+    }
+    
+    /**
+     * Writes the default task names for the current project.
+     *
+     * @param defaultTaskNames The default task names (must not be null)
+     */
+    public void addDefaultTasks(List<String> defaultTaskNames) {
+        if (defaultTaskNames.size() > 0) {
+            getTextOutput().formatln("Default tasks: %s", CollectionUtils.join(", ", defaultTaskNames));
+            hasContent = true;
+        }
+    }
+
+    public void startTaskGroup(String taskGroup) {
+        if (!GUtil.isTrue(taskGroup)) {
+            addSubheading("Tasks");
+        } else {
+            addSubheading(StringUtils.capitalize(taskGroup) + " tasks");
+        }
+        currentProjectHasTasks = true;
+    }
+
+    /**
+     * Writes a task for the current project.
+     *
+     * @param task The task
+     */
+    public void addTask(TaskDetails task) {
+        writeTask(task, "");
+    }
+
+    public void addChildTask(TaskDetails task) {
+        if (detail) {
+            writeTask(task, "    ");
+        }
+    }
+
+    private void writeTask(TaskDetails task, String prefix) {
+        getTextOutput().text(prefix);
+        getTextOutput().withStyle(Identifier).text(task.getPath());
+        if (GUtil.isTrue(task.getDescription())) {
+            getTextOutput().withStyle(Description).format(" - %s", task.getDescription());
+        }
+        if (detail) {
+            SortedSet<Path> sortedDependencies = new TreeSet<Path>();
+            for (TaskDetails dependency : task.getDependencies()) {
+                sortedDependencies.add(dependency.getPath());
+            }
+            if (sortedDependencies.size() > 0) {
+                getTextOutput().withStyle(Info).format(" [%s]", CollectionUtils.join(", ", sortedDependencies));
+            }
+        }
+        getTextOutput().println();
+    }
+
+    private void addSubheading(String header) {
+        if (hasContent) {
+            getTextOutput().println();
+        }
+        hasContent = true;
+        writeSubheading(header);
+    }
+
+    /**
+     * Marks the end of the tasks for the current project.
+     */
+    public void completeTasks() {
+        if (!currentProjectHasTasks) {
+            getTextOutput().withStyle(Info).println("No tasks");
+            hasContent = true;
+        }
+    }
+
+    /**
+     * Writes a rule for the current project.
+     *
+     * @param rule The rule
+     */
+    public void addRule(Rule rule) {
+        if (!currentProjectHasRules) {
+            addSubheading("Rules");
+        }
+        getTextOutput().println(GUtil.elvis(rule.getDescription(), ""));
+        currentProjectHasRules = true;
+    }
+
+    @Override
+    public void complete() throws IOException {
+        if (!detail) {
+            getTextOutput().println();
+            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/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
rename to subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java
new file mode 100644
index 0000000..61c33c9
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRenderer.java
@@ -0,0 +1,112 @@
+/*
+ * 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.tasks.diagnostics.internal.dependencies;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.result.ResolutionResult;
+import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
+import org.gradle.api.tasks.diagnostics.internal.GraphRenderer;
+import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
+import org.gradle.api.tasks.diagnostics.internal.graph.DependencyGraphRenderer;
+import org.gradle.api.tasks.diagnostics.internal.graph.NodeRenderer;
+import org.gradle.api.tasks.diagnostics.internal.graph.SimpleNodeRenderer;
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency;
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.util.GUtil;
+
+import java.io.IOException;
+
+import static org.gradle.logging.StyledTextOutput.Style.*;
+
+/**
+ * Simple dependency graph renderer that emits an ASCII tree.
+ *
+ * @author Phil Messenger
+ */
+public class AsciiDependencyReportRenderer extends TextReportRenderer implements DependencyReportRenderer {
+    private boolean hasConfigs;
+    DependencyGraphRenderer dependencyGraphRenderer;
+
+    @Override
+    public void startProject(Project project) {
+        super.startProject(project);
+        hasConfigs = false;
+    }
+
+    @Override
+    public void completeProject(Project project) {
+        if (!hasConfigs) {
+            getTextOutput().withStyle(Info).println("No configurations");
+        }
+        super.completeProject(project);
+    }
+
+    public void startConfiguration(final Configuration configuration) {
+        if (hasConfigs) {
+            getTextOutput().println();
+        }
+        hasConfigs = true;
+        GraphRenderer renderer = new GraphRenderer(getTextOutput());
+        renderer.visit(new Action<StyledTextOutput>() {
+            public void execute(StyledTextOutput styledTextOutput) {
+                getTextOutput().withStyle(Identifier).text(configuration.getName());
+                getTextOutput().withStyle(Description).text(getDescription(configuration));
+            }
+        }, true);
+
+        NodeRenderer nodeRenderer = new SimpleNodeRenderer();
+        dependencyGraphRenderer = new DependencyGraphRenderer(renderer, nodeRenderer);
+    }
+
+    private String getDescription(Configuration configuration) {
+        return GUtil.isTrue(configuration.getDescription()) ? " - " + configuration.getDescription() : "";
+    }
+
+    public void completeConfiguration(Configuration configuration) {}
+
+    public void render(Configuration configuration) throws IOException {
+        ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration();
+        ResolutionResult result = configuration.getIncoming().getResolutionResult();
+        RenderableDependency root = new RenderableModuleResult(result.getRoot());
+
+        renderNow(root);
+
+        resolvedConfiguration.rethrowFailure();
+    }
+
+    void renderNow(RenderableDependency root) {
+        if (root.getChildren().isEmpty()) {
+            getTextOutput().withStyle(Info).text("No dependencies");
+            getTextOutput().println();
+            return;
+        }
+
+        dependencyGraphRenderer.render(root);
+    }
+
+    public void complete() throws IOException {
+        if (dependencyGraphRenderer != null) {
+            dependencyGraphRenderer.printLegend();
+        }
+
+        super.complete();
+    }
+
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpec.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpec.java
new file mode 100644
index 0000000..5bcdf10
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpec.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.tasks.diagnostics.internal.dsl;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.specs.Spec;
+
+/**
+* by Szczepan Faber, created at: 11/6/12
+*/
+class DependencyResultSpec implements Spec<DependencyResult> {
+    private final String stringNotation;
+
+    public DependencyResultSpec(String stringNotation) {
+        this.stringNotation = stringNotation;
+    }
+
+    public boolean isSatisfiedBy(DependencyResult candidate) {
+        //The matching is very simple at the moment but it should solve majority of cases.
+        //It operates using String#contains and it tests either requested or selected module.
+        if (candidate instanceof ResolvedDependencyResult) {
+            return matchesRequested(candidate) || matchesSelected((ResolvedDependencyResult) candidate);
+        } else {
+            return matchesRequested(candidate);
+        }
+    }
+
+    private boolean matchesRequested(DependencyResult candidate) {
+        String requestedCandidate = candidate.getRequested().getGroup() + ":" + candidate.getRequested().getName() + ":" + candidate.getRequested().getVersion();
+        return requestedCandidate.contains(stringNotation);
+    }
+
+    private boolean matchesSelected(ResolvedDependencyResult candidate) {
+        ModuleVersionIdentifier selected = candidate.getSelected().getId();
+        String selectedCandidate = selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
+        return selectedCandidate.contains(stringNotation);
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParser.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParser.java
new file mode 100644
index 0000000..1b8f16b
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParser.java
@@ -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.tasks.diagnostics.internal.dsl;
+
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.TypeInfo;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.api.internal.notations.parsers.ClosureToSpecNotationParser;
+import org.gradle.api.specs.Spec;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+public class DependencyResultSpecNotationParser implements NotationParser<Spec<DependencyResult>> {
+
+    public Spec<DependencyResult> parseNotation(final Object notation) throws UnsupportedNotationException {
+        if (notation instanceof CharSequence) {
+            final String stringNotation = notation.toString().trim();
+            if (stringNotation.length() > 0) {
+                return new DependencyResultSpec(stringNotation);
+            }
+        }
+        throw new UnsupportedNotationException(notation);
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Non-empty String value, e.g. 'some-lib' or 'org.libs:some-lib'.");
+        candidateFormats.add("Closure that returns boolean and takes a single DependencyResult as parameter.");
+    }
+
+    public static NotationParser<Spec<DependencyResult>> create() {
+        return new NotationParserBuilder<Spec<DependencyResult>>()
+                .resultingType(new TypeInfo<Spec<DependencyResult>>(Spec.class))
+                .invalidNotationMessage("Please check the input for the DependencyInsight.dependency element.")
+                .parser(new ClosureToSpecNotationParser<DependencyResult>())
+                .parser(new DependencyResultSpecNotationParser())
+                .toComposite();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.groovy
new file mode 100644
index 0000000..0473c9f
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRenderer.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.api.tasks.diagnostics.internal.graph
+
+import org.gradle.api.Action
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.tasks.diagnostics.internal.GraphRenderer
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.logging.StyledTextOutput
+
+import static org.gradle.logging.StyledTextOutput.Style.Info
+
+/**
+ * by Szczepan Faber, created at: 9/20/12
+ */
+class DependencyGraphRenderer {
+
+    GraphRenderer renderer
+    NodeRenderer nodeRenderer
+    boolean hasCyclicDependencies = false
+
+    DependencyGraphRenderer(GraphRenderer renderer, NodeRenderer nodeRenderer) {
+        this.renderer = renderer
+        this.nodeRenderer = nodeRenderer
+    }
+
+    void render(RenderableDependency root) {
+        def visited = new HashSet<ModuleVersionIdentifier>()
+        visited.add(root.getId())
+        renderChildren(root.getChildren(), visited);
+    }
+
+    private void renderChildren(Set<? extends RenderableDependency> children, Set<ModuleVersionIdentifier> visited) {
+        renderer.startChildren();
+        int i = 0;
+        for (RenderableDependency child : children) {
+            boolean last = i++ == children.size() - 1;
+            render(child, last, visited);
+        }
+        renderer.completeChildren();
+    }
+
+    private void render(final RenderableDependency parent, boolean last, Set<ModuleVersionIdentifier> visited) {
+        def children = parent.getChildren();
+        boolean alreadyRendered = !visited.add(parent.getId())
+        if (alreadyRendered) {
+            hasCyclicDependencies = true
+        }
+
+        renderer.visit(new Action<StyledTextOutput>() {
+            public void execute(StyledTextOutput output) {
+                nodeRenderer.renderNode(output, parent, children, alreadyRendered);
+            }
+        }, last);
+
+        if (!alreadyRendered) {
+            renderChildren(children, visited);
+        }
+    }
+
+    void printLegend() {
+        if (hasCyclicDependencies) {
+            renderer.output.println()
+            renderer.output.withStyle(Info).println("(*) - dependencies omitted (listed previously)");
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.groovy
new file mode 100644
index 0000000..ee4f888
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/NodeRenderer.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.api.tasks.diagnostics.internal.graph
+
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.logging.StyledTextOutput
+
+/**
+ * by Szczepan Faber, created at: 9/20/12
+ */
+interface NodeRenderer {
+    void renderNode(StyledTextOutput output, RenderableDependency node,
+                    Set<RenderableDependency>children, boolean alreadyRendered);
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.java
new file mode 100644
index 0000000..b9a743b
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/SimpleNodeRenderer.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.tasks.diagnostics.internal.graph;
+
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency;
+import org.gradle.logging.StyledTextOutput;
+
+import java.util.Set;
+
+import static org.gradle.logging.StyledTextOutput.Style.Info;
+
+/**
+* by Szczepan Faber, created at: 9/21/12
+*/
+public class SimpleNodeRenderer implements NodeRenderer {
+    public void renderNode(StyledTextOutput output, RenderableDependency node, Set<RenderableDependency> children, boolean alreadyRendered) {
+        output.text(node.getName());
+        if (alreadyRendered) {
+            output.withStyle(Info).text(" (*)");
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
new file mode 100644
index 0000000..84abb38
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.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.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 7/27/12
+ */
+public abstract class AbstractRenderableDependencyResult implements RenderableDependency {
+
+    protected final ResolvedDependencyResult dependency;
+    protected final String description;
+
+    public AbstractRenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
+        this.dependency = dependency;
+        this.description = description;
+    }
+
+    public String getName() {
+        if (!requestedEqualsSelected(dependency)) {
+            return getVerboseName();
+        } else {
+            return requested();
+        }
+    }
+
+    private String getVerboseName() {
+        ModuleVersionSelector requested = dependency.getRequested();
+        ModuleVersionIdentifier selected = dependency.getSelected().getId();
+        if(!selected.getGroup().equals(requested.getGroup())) {
+            return requested() + " -> " + selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
+        } else if (!selected.getName().equals(requested.getName())) {
+            return requested() + " -> " + selected.getName() + ":" + selected.getVersion();
+        } else if (!selected.getVersion().equals(requested.getVersion())) {
+            return requested() + " -> " + selected.getVersion();
+        } else {
+            return requested();
+        }
+    }
+
+    private static boolean requestedEqualsSelected(ResolvedDependencyResult dependency) {
+        return dependency.getRequested().matchesStrictly(dependency.getSelected().getId());
+    }
+
+    private String requested() {
+        return dependency.getRequested().getGroup() + ":" + dependency.getRequested().getName() + ":" + dependency.getRequested().getVersion();
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return dependency.getSelected().getId();
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public abstract Set<RenderableDependency> getChildren();
+
+    @Override
+    public String toString() {
+        return dependency.toString();
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.java
new file mode 100644
index 0000000..1bb911e
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableModuleResult.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.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 8/10/12
+ */
+public abstract class AbstractRenderableModuleResult implements RenderableDependency {
+
+    protected final ResolvedModuleVersionResult module;
+
+    public AbstractRenderableModuleResult(ResolvedModuleVersionResult module) {
+        this.module = module;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return module.getId();
+    }
+
+    public String getName() {
+        return module.getId().getGroup() + ":" + module.getId().getName() + ":" + module.getId().getVersion();
+    }
+
+    public String getDescription() {
+        return null;
+    }
+
+    public abstract Set<RenderableDependency> getChildren();
+
+    @Override
+    public String toString() {
+        return module.toString();
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.java
new file mode 100644
index 0000000..33fee40
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResult.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.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Children of this renderable dependency node are its dependents.
+ *
+ * by Szczepan Faber, created at: 7/27/12
+ */
+public class InvertedRenderableDependencyResult extends RenderableDependencyResult implements RenderableDependency {
+
+    public InvertedRenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
+        super(dependency, description);
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
+        for (ResolvedDependencyResult r : dependency.getSelected().getDependents()) {
+            //we want only the dependents that match the requested
+            if (r.getRequested().equals(this.dependency.getRequested())) {
+                out.add(new InvertedRenderableModuleResult(r.getFrom()));
+            }
+        }
+
+        return out;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.java
new file mode 100644
index 0000000..ce59349
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableModuleResult.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.tasks.diagnostics.internal.graph.nodes;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Children of this renderable dependency node are its dependents.
+ *
+ * by Szczepan Faber, created at: 8/10/12
+ */
+public class InvertedRenderableModuleResult extends RenderableModuleResult implements RenderableDependency {
+
+    public InvertedRenderableModuleResult(ResolvedModuleVersionResult module) {
+        super(module);
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        return new LinkedHashSet(Collections2.transform(module.getDependents(), new Function<ResolvedDependencyResult, RenderableDependency>() {
+            public RenderableDependency apply(ResolvedDependencyResult input) {
+                return new InvertedRenderableModuleResult(input.getFrom());
+            }
+        }));
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.java
new file mode 100644
index 0000000..7a4713a
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependency.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.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.Set;
+
+/**
+* by Szczepan Faber, created at: 7/27/12
+*/
+public interface RenderableDependency {
+    ModuleVersionIdentifier getId();
+    String getName();
+    String getDescription();
+    Set<RenderableDependency> getChildren();
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.java
new file mode 100644
index 0000000..59cdbcb
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResult.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.api.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 7/27/12
+ */
+public class RenderableDependencyResult extends AbstractRenderableDependencyResult implements RenderableDependency {
+
+    public RenderableDependencyResult(ResolvedDependencyResult dependency) {
+        this(dependency, null);
+    }
+
+    public RenderableDependencyResult(ResolvedDependencyResult dependency, String description) {
+        super(dependency, description);
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
+        for (DependencyResult d : dependency.getSelected().getDependencies()) {
+            //TODO SF revisit when implementing the 'unresolved dependencies' story
+            if (d instanceof ResolvedDependencyResult) {
+                out.add(new RenderableDependencyResult((ResolvedDependencyResult) d));
+            }
+        }
+        return out;
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.java
new file mode 100644
index 0000000..a693e1f
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableModuleResult.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.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.result.DependencyResult;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 8/10/12
+ */
+public class RenderableModuleResult extends AbstractRenderableModuleResult implements RenderableDependency {
+
+    public RenderableModuleResult(ResolvedModuleVersionResult module) {
+        super(module);
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        Set<RenderableDependency> out = new LinkedHashSet<RenderableDependency>();
+        for (DependencyResult d : module.getDependencies()) {
+            //TODO SF revisit when implementing the 'unresolved dependencies' story
+            if (d instanceof ResolvedDependencyResult) {
+                out.add(new RenderableDependencyResult((ResolvedDependencyResult) d));
+            }
+        }
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.java
new file mode 100644
index 0000000..590250c
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/SimpleDependency.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.tasks.diagnostics.internal.graph.nodes;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId;
+
+public class SimpleDependency implements RenderableDependency {
+
+    private final ModuleVersionIdentifier id;
+    private final String name;
+    private final String description;
+    private final Set<RenderableDependency> children = new LinkedHashSet<RenderableDependency>();
+
+    public SimpleDependency(String name) {
+        this(name, null);
+    }
+
+    public SimpleDependency(String name, String description) {
+        this.name = name;
+        this.description = description;
+        this.id = newId(name, name, "1.0");
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Set<RenderableDependency> getChildren() {
+        return children;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy
new file mode 100644
index 0000000..af1f9a1
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporter.groovy
@@ -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.tasks.diagnostics.internal.insight;
+
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.InvertedRenderableDependencyResult
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.SimpleDependency
+
+/**
+ * Created: 23/08/2012
+ *
+ * @author Szczepan Faber
+ */
+public class DependencyInsightReporter {
+
+    Collection<RenderableDependency> prepare(Collection<ResolvedDependencyResult> input) {
+        def out = new LinkedList<RenderableDependency>()
+        def sorted = ResolvedDependencyResultSorter.sort(input)
+
+        //remember if module id was annotated
+        def annotated = new HashSet<ModuleVersionIdentifier>()
+
+        for (ResolvedDependencyResult dependency: sorted) {
+            def description = null
+            //add description only to the first module
+            if (annotated.add(dependency.selected.id)) {
+                //add a heading dependency with the annotation if the dependency does not exist in the graph
+                if (!dependency.requested.matchesStrictly(dependency.selected.id)) {
+                    def name = dependency.selected.id.group + ":" + dependency.selected.id.name + ":" + dependency.selected.id.version
+                    out << new SimpleDependency(name, describeReason(dependency.selected.selectionReason))
+                } else {
+                    description = describeReason(dependency.selected.selectionReason)
+                }
+            }
+
+            out << new InvertedRenderableDependencyResult(dependency, description)
+        }
+
+        out
+    }
+
+    private String describeReason(ModuleVersionSelectionReason reason) {
+        if (reason.conflictResolution) {
+            return "conflict resolution"
+        } else if (reason.forced) {
+            return "forced"
+        } else {
+            return null
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.java
new file mode 100644
index 0000000..4e9c4a3
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorter.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.tasks.diagnostics.internal.insight;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.result.ResolvedDependencyResult;
+import org.gradle.api.internal.artifacts.version.LatestVersionSemanticComparator;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+ * Created: 17/08/2012
+ *
+ * @author Szczepan Faber
+ */
+public class ResolvedDependencyResultSorter {
+
+    /**
+     * sorts by group:name:version mostly.
+     * If requested matches selected then it will override the version comparison
+     * so that the dependency that was selected is more prominent.
+     */
+    public static Collection<ResolvedDependencyResult> sort(Collection<ResolvedDependencyResult> input) {
+        //dependencies with the same 'requested' should be presented in a single tree
+        final Set<ModuleVersionSelector> uniqueRequested = new HashSet<ModuleVersionSelector>();
+        List<ResolvedDependencyResult> out = CollectionUtils.filter(input, new LinkedList<ResolvedDependencyResult>(), new Spec<ResolvedDependencyResult>() {
+            public boolean isSatisfiedBy(ResolvedDependencyResult element) {
+                return uniqueRequested.add(element.getRequested());
+            }
+        });
+        Collections.sort(out, new DependencyComparator());
+        return out;
+    }
+
+    private static class DependencyComparator implements Comparator<ResolvedDependencyResult> {
+
+        private final LatestVersionSemanticComparator versionComparator = new LatestVersionSemanticComparator();
+
+        public int compare(ResolvedDependencyResult left, ResolvedDependencyResult right) {
+            int byGroup = left.getRequested().getGroup().compareTo(right.getRequested().getGroup());
+            if (byGroup != 0) {
+                return byGroup;
+            }
+
+            int byModule = left.getRequested().getName().compareTo(right.getRequested().getName());
+            if (byModule != 0) {
+                return byModule;
+            }
+
+            //if selected matches requested version comparison is overridden
+            if (left.getRequested().matchesStrictly(left.getSelected().getId())) {
+                return -1;
+            } else if (right.getRequested().matchesStrictly(right.getSelected().getId())) {
+                return 1;
+            }
+
+            return versionComparator.compare(left.getRequested().getVersion(), right.getRequested().getVersion());
+        }
+    }
+}
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java
new file mode 100644
index 0000000..b69b9b3
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.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.
+ */
+
+/**
+ * The diagnostic {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.diagnostics;
+
+
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java
new file mode 100644
index 0000000..2621929
--- /dev/null
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/configuration/Help.java
@@ -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.configuration;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.util.GradleVersion;
+
+import static org.gradle.logging.StyledTextOutput.Style.UserInput;
+
+public class Help extends DefaultTask {
+    @TaskAction
+    void displayHelp() {
+        StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(Help.class);
+        BuildClientMetaData metaData = getServices().get(BuildClientMetaData.class);
+
+        output.println();
+        output.formatln("Welcome to Gradle %s.", GradleVersion.current().getVersion());
+        output.println();
+        output.text("To run a build, run ");
+        metaData.describeCommand(output.withStyle(UserInput), "<task> ...");
+        output.println();
+        output.println();
+        output.text("To see a list of available tasks, run ");
+        metaData.describeCommand(output.withStyle(UserInput), "tasks");
+        output.println();
+        output.println();
+        output.text("To see a list of command-line options, run ");
+        metaData.describeCommand(output.withStyle(UserInput), "--help");
+        output.println();
+    }
+}
diff --git a/subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/help-tasks.properties b/subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/help-tasks.properties
new file mode 100644
index 0000000..e3f9f97
--- /dev/null
+++ b/subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/help-tasks.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.HelpTasksPlugin
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/project-report.properties b/subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/project-report.properties
similarity index 100%
rename from subprojects/plugins/src/main/resources/META-INF/gradle-plugins/project-report.properties
rename to subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/project-report.properties
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/project-reports.properties b/subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/project-reports.properties
similarity index 100%
rename from subprojects/plugins/src/main/resources/META-INF/gradle-plugins/project-reports.properties
rename to subprojects/diagnostics/src/main/resources/META-INF/gradle-plugins/project-reports.properties
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/HelpTasksPluginSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/HelpTasksPluginSpec.groovy
new file mode 100644
index 0000000..cdfccf8
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/HelpTasksPluginSpec.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.api.plugins
+
+import org.gradle.configuration.Help
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+import org.gradle.api.tasks.diagnostics.*
+
+import static org.gradle.configuration.ImplicitTasksConfigurer.*
+
+/**
+ * by Szczepan Faber, created at: 9/5/12
+ */
+class HelpTasksPluginSpec extends Specification {
+
+    def project = new ProjectBuilder().build()
+
+    def "adds help tasks"() {
+        when:
+        project.apply(plugin: 'help-tasks')
+
+        then:
+        hasHelpTask(HELP_TASK, Help)
+        hasHelpTask(DEPENDENCY_INSIGHT_TASK, DependencyInsightReportTask)
+        hasHelpTask(DEPENDENCIES_TASK, DependencyReportTask)
+        hasHelpTask(PROJECTS_TASK, ProjectReportTask)
+        hasHelpTask(TASKS_TASK, TaskReportTask)
+        hasHelpTask(PROPERTIES_TASK, PropertyReportTask)
+    }
+
+    def "configures tasks for java plugin"() {
+        when:
+        project.apply(plugin: 'help-tasks')
+
+        then:
+        !project.implicitTasks[DEPENDENCY_INSIGHT_TASK].configuration
+
+        when:
+        project.plugins.apply(JavaPlugin)
+
+        then:
+        project.implicitTasks[DEPENDENCY_INSIGHT_TASK].configuration == project.configurations.compile
+    }
+
+    private void hasHelpTask(String name, Class type) {
+        def task = project.implicitTasks.getByName(name)
+        assert type.isInstance(task)
+        assert task.group == HELP_GROUP
+        if (type != Help.class) {
+            assert task.description.contains(project.name)
+        }
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
new file mode 100644
index 0000000..1e3ccde
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
@@ -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
+
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+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/core/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.groovy
new file mode 100644
index 0000000..0a7c5b4
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskSpec.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.api.tasks.diagnostics
+
+import org.gradle.api.specs.Spec
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+import org.gradle.api.InvalidUserDataException
+
+/**
+ * by Szczepan Faber, created at: 9/20/12
+ */
+class DependencyInsightReportTaskSpec extends Specification {
+
+    def project = ProjectBuilder.builder().build()
+    def task = project.tasks.add("insight", DependencyInsightReportTask)
+
+    def "fails if configuration missing"() {
+        when:
+        task.report()
+
+        then:
+        thrown(ReportException)
+    }
+
+    def "fails if dependency to include missing"() {
+        def conf = project.configurations.add("foo")
+        task.configuration = conf
+
+        when:
+        task.report()
+
+        then:
+        thrown(ReportException)
+    }
+
+    def "fails if dependency to include is empty"() {
+        when:
+        task.setDependencySpec("")
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "can set spec and configuration directly"() {
+        when:
+        def conf = project.configurations.add("foo")
+        task.configuration = conf
+        task.dependencySpec = { true } as Spec
+        then:
+        task.dependencySpec != null
+        task.configuration == conf
+    }
+
+    def "can set spec and configuration via methods"() {
+        when:
+        project.configurations.add("foo")
+        task.setConfiguration 'foo'
+        task.setDependencySpec 'bar'
+        then:
+        task.dependencySpec != null
+        task.configuration.name == 'foo'
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.groovy
new file mode 100644
index 0000000..e9dbf62
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.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.api.tasks.diagnostics;
+
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer
+import org.gradle.api.tasks.diagnostics.internal.dependencies.AsciiDependencyReportRenderer
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+public class DependencyReportTaskTest extends Specification {
+
+    private Project project = new ProjectBuilder().build()
+    private DependencyReportTask task = HelperUtil.createTask(DependencyReportTask.class);
+    private DependencyReportRenderer renderer = Mock(DependencyReportRenderer)
+
+    void setup() {
+        task.renderer = renderer
+    }
+
+    def "task is configured correctly"() {
+        task = HelperUtil.createTask(DependencyReportTask.class);
+
+        expect:
+        task.renderer instanceof AsciiDependencyReportRenderer
+        task.configurations == null
+    }
+
+    def "uses project configurations"() {
+        given:
+        def bConf = project.configurations.add("b-conf")
+        def aConf = project.configurations.add("a-conf")
+
+        when:
+        task.generate(project)
+
+        then: 1 * renderer.startConfiguration(aConf)
+        then: 1 * renderer.render(aConf)
+        then: 1 * renderer.completeConfiguration(aConf)
+
+
+        then: 1 * renderer.startConfiguration(bConf)
+        then: 1 * renderer.render(bConf)
+        then: 1 * renderer.completeConfiguration(bConf)
+
+        0 * renderer._
+    }
+
+    def "uses specific configurations"() {
+        given:
+        project.configurations.add("a")
+        def bConf = project.configurations.add("b")
+        task.configurations = [bConf] as Set
+
+        when:
+        task.generate(project)
+
+        then:
+        1 * renderer.startConfiguration(bConf)
+        1 * renderer.render(bConf)
+        1 * renderer.completeConfiguration(bConf)
+        0 * renderer._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AbstractTaskModelSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AbstractTaskModelSpec.groovy
new file mode 100644
index 0000000..3400ef2
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AbstractTaskModelSpec.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.api.tasks.diagnostics.internal
+
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.util.Path
+import spock.lang.Specification
+
+abstract class AbstractTaskModelSpec extends Specification {
+    def taskDetails(String path) {
+        return taskDetails([:], path)
+    }
+
+    def taskDetails(Map properties, String path) {
+        TaskDetails task = Mock()
+        _ * task.path >> Path.path(path)
+        _ * task.toString() >> path
+        _ * task.description >> properties.description
+        _ * task.dependencies >> ((properties.dependencies ?: []) as Set)
+        return task
+    }
+
+    def task(String name, String group = null, Task... dependencies) {
+        Task task = Mock()
+        _ * task.toString() >> name
+        _ * task.name >> name
+        _ * task.path >> ":$name"
+        _ * task.group >> group
+        _ * task.compareTo(!null) >> { args -> name.compareTo(args[0].name) }
+        TaskDependency dep = Mock()
+        _ * dep.getDependencies(task) >> {dependencies as Set}
+        _ * task.taskDependencies >> dep
+        return task
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.groovy
new file mode 100644
index 0000000..1c2a6d6
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AggregateMultiProjectTaskReportModelTest.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
+
+class AggregateMultiProjectTaskReportModelTest extends AbstractTaskModelSpec {
+    final AggregateMultiProjectTaskReportModel model = new AggregateMultiProjectTaskReportModel(false)
+
+    def mergesTheGroupsFromEachProject() {
+        TaskReportModel project1 = Mock()
+        TaskReportModel project2 = Mock()
+        _ * project1.groups >> (['p1', 'common'] as LinkedHashSet)
+        _ * project2.groups >> (['p2', 'common'] as LinkedHashSet)
+        _ * _.getTasksForGroup(_) >> ([taskDetails('task')] as Set)
+
+        when:
+        model.add(project1)
+        model.add(project2)
+        model.build()
+
+        then:
+        model.groups == ['p1', 'common', 'p2'] as Set
+    }
+
+    def mergesTheGroupsWithAGivenNameFromEachProjectIntoASingleGroup() {
+        TaskDetails task1 = taskDetails('task1')
+        TaskDetails task2 = taskDetails('task2')
+        TaskReportModel project1 = Mock()
+        TaskReportModel project2 = Mock()
+        _ * project1.groups >> (['group'] as LinkedHashSet)
+        _ * project1.getTasksForGroup('group') >> ([task1] as Set)
+        _ * project2.groups >> (['group'] as LinkedHashSet)
+        _ * project2.getTasksForGroup('group') >> ([task2] as Set)
+
+        when:
+        model.add(project1)
+        model.add(project2)
+        model.build()
+
+        then:
+        model.getTasksForGroup('group') == [task1, task2] as Set
+    }
+
+    def mergesTheTasksWithAGivenNameFromEachProjectIntoASingleTask() {
+        TaskDetails task1 = taskDetails(':task')
+        TaskDetails task2 = taskDetails(':other')
+        TaskDetails task3 = taskDetails(':sub:task')
+        TaskReportModel project1 = Mock()
+        TaskReportModel project2 = Mock()
+        _ * project1.groups >> (['group'] as LinkedHashSet)
+        _ * project1.getTasksForGroup('group') >> ([task1, task2] as Set)
+        _ * project2.groups >> (['group'] as LinkedHashSet)
+        _ * project2.getTasksForGroup('group') >> ([task3] as Set)
+
+        def model = new AggregateMultiProjectTaskReportModel(true)
+
+        when:
+        model.add(project1)
+        model.add(project2)
+        model.build()
+
+        then:
+        model.getTasksForGroup('group')*.path*.path as Set == ['task', 'other'] as Set
+    }
+
+    def handlesGroupWhichIsNotPresentInEachProject() {
+        TaskDetails task1 = taskDetails('task1')
+        TaskReportModel project1 = Mock()
+        TaskReportModel project2 = Mock()
+        _ * project1.groups >> (['group'] as LinkedHashSet)
+        _ * project1.getTasksForGroup('group') >> ([task1] as Set)
+        _ * project2.groups >> ([] as LinkedHashSet)
+        0 * project2.getTasksForGroup(_)
+
+        when:
+        model.add(project1)
+        model.add(project2)
+        model.build()
+
+        then:
+        model.getTasksForGroup('group') == [task1] as Set
+    }
+
+    def groupNamesAreCaseInsensitive() {
+        TaskDetails task1 = taskDetails('task1')
+        TaskDetails task2 = taskDetails('task2')
+        TaskReportModel project1 = Mock()
+        TaskReportModel project2 = Mock()
+        _ * project1.groups >> (['A'] as LinkedHashSet)
+        _ * project1.getTasksForGroup('A') >> ([task1] as Set)
+        _ * project2.groups >> (['a'] as LinkedHashSet)
+        _ * project2.getTasksForGroup('a') >> ([task2] as Set)
+
+        when:
+        model.add(project1)
+        model.add(project2)
+        model.build()
+
+        then:
+        model.groups == ['A'] as Set
+        model.getTasksForGroup('A') == [task1, task2] as Set
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy
new file mode 100644
index 0000000..94548b2
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/DefaultGroupTaskReportModelTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.diagnostics.internal
+
+class DefaultGroupTaskReportModelTest extends AbstractTaskModelSpec {
+    final TaskReportModel target = Mock()
+    final DefaultGroupTaskReportModel model = new DefaultGroupTaskReportModel()
+
+    def mergesDefaultGroupIntoOtherGroup() {
+        TaskDetails task1 = taskDetails('1')
+        TaskDetails task2 = taskDetails('2')
+        TaskDetails task3 = taskDetails('3')
+        _ * target.groups >> ['a', '', 'other']
+        _ * target.getTasksForGroup('a') >> [task1]
+        _ * target.getTasksForGroup('') >> [task2]
+        _ * target.getTasksForGroup('other') >> [task3]
+        
+        when:
+        model.build(target)
+
+        then:
+
+        model.groups as List == ['a', 'other']
+        model.getTasksForGroup('a') as List == [task1]
+        model.getTasksForGroup('other') as List == [task2, task3]
+    }
+
+    def groupNamesAreOrderedCaseInsensitive() {
+        TaskDetails task1 = taskDetails('task1')
+        TaskDetails task2 = taskDetails('task2')
+        TaskDetails task3 = taskDetails('task3')
+        TaskDetails task4 = taskDetails('task4')
+        TaskDetails task5 = taskDetails('task5')
+
+        _ * target.groups >> (['Abc', 'a', 'A', '', 'Other'] as LinkedHashSet)
+        _ * target.getTasksForGroup('a') >> [task1]
+        _ * target.getTasksForGroup('A') >> [task2]
+        _ * target.getTasksForGroup('Abc') >> [task3]
+        _ * target.getTasksForGroup('') >> [task4]
+        _ * target.getTasksForGroup('Other') >> [task5]
+
+        when:
+        model.build(target)
+
+        then:
+        model.groups as List == ['A', 'a', 'Abc', 'Other']
+        model.getTasksForGroup('Other') as List == [task4, task5]
+    }
+
+    def taskNamesAreOrderedCaseInsensitiveByNameThenPath() {
+        def task1 = taskDetails('task_A')
+        def task2 = taskDetails('a:task_A')
+        def task3 = taskDetails('a:a:task_A')
+        def task4 = taskDetails('B:task_A')
+        def task5 = taskDetails('c:task_A')
+        def task6 = taskDetails('b:task_a')
+        def task7 = taskDetails('a:task_Abc')
+        def task8 = taskDetails('task_b')
+        _ * target.groups >> ['group']
+        _ * target.getTasksForGroup('group') >> ([task6, task3, task7, task4, task5, task1, task8, task2] as LinkedHashSet)
+
+        when:
+        model.build(target)
+
+        then:
+        model.getTasksForGroup('group') as List == [task1, task2, task3, task4, task5, task6, task7, task8]
+    }
+
+    def renamesDefaultGroupWhenOtherGroupNotPresent() {
+        TaskDetails task1 = taskDetails('1')
+        TaskDetails task2 = taskDetails('2')
+        _ * target.groups >> ['a', '']
+        _ * target.getTasksForGroup('a') >> [task1]
+        _ * target.getTasksForGroup('') >> [task2]
+
+        when:
+        model.build(target)
+
+        then:
+        model.groups as List == ['a', 'other']
+        model.getTasksForGroup('a') as List == [task1]
+        model.getTasksForGroup('other') as List == [task2]
+    }
+
+    def doesNotRenameDefaultGroupWhenItIsTheOnlyGroup() {
+        TaskDetails task1 = taskDetails('1')
+        _ * target.groups >> ['']
+        _ * target.getTasksForGroup('') >> [task1]
+
+        when:
+        model.build(target)
+
+        then:
+        model.groups as List == ['']
+        model.getTasksForGroup('') as List == [task1]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
rename to subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy
new file mode 100644
index 0000000..b0345a9
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModelTest.groovy
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.util.Path
+
+class SingleProjectTaskReportModelTest extends AbstractTaskModelSpec {
+    final TaskDetailsFactory factory = Mock()
+    final SingleProjectTaskReportModel model = new SingleProjectTaskReportModel(factory)
+
+    def setup() {
+        _ * factory.create(!null) >> {args ->
+            def task = args[0]
+            [getPath: { Path.path(task.path) }, getName: { task.name }] as TaskDetails
+        }
+    }
+
+    def groupsTasksBasedOnTheirGroup() {
+        def task1 = task('task1', 'group1')
+        def task2 = task('task2', 'group2')
+        def task3 = task('task3', 'group1')
+
+        when:
+        model.build([task1, task2, task3])
+
+        then:
+        model.groups == ['group1', 'group2'] as Set
+        model.getTasksForGroup('group1')*.task == [task1, task3]
+        model.getTasksForGroup('group2')*.task == [task2]
+    }
+
+    def groupsAreTreatedAsCaseInsensitive() {
+        def task1 = task('task1', 'a')
+        def task2 = task('task2', 'B')
+        def task3 = task('task3', 'b')
+        def task4 = task('task4', 'c')
+
+        when:
+        model.build([task2, task3, task4, task1])
+
+        then:
+        model.groups == ['a', 'B', 'c'] as Set
+        model.getTasksForGroup('a')*.task == [task1]
+        model.getTasksForGroup('B')*.task == [task2, task3]
+        model.getTasksForGroup('c')*.task == [task4]
+    }
+
+    def tasksWithNoGroupAreTreatedAsChildrenOfTheNearestTopLevelTaskTheyAreReachableFrom() {
+        def task1 = task('task1')
+        def task2 = task('task2')
+        def task3 = task('task3', 'group1', task1, task2)
+        def task4 = task('task4', task3, task1)
+        def task5 = task('task5', 'group2', task4)
+
+        when:
+        model.build([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails task3Details = (model.getTasksForGroup('group1') as List).first()
+        task3Details.task == task3
+        task3Details.children*.task == [task1, task2]
+
+        TaskDetails task5Details = (model.getTasksForGroup('group2') as List).first()
+        task5Details.task == task5
+        task5Details.children*.task == [task4]
+    }
+
+    def theDependenciesOfATopLevelTaskAreTheUnionOfTheDependenciesOfItsChildren() {
+        def task1 = task('task1', 'group1')
+        def task2 = task('task2', 'group2', task1)
+        def task3 = task('task3', 'group3')
+        def task4 = task('task4', task2)
+        def task5 = task('task5', 'group4', task3, task4)
+
+        when:
+        model.build([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group4') as List).first()
+        t.dependencies*.path*.name as Set == ['task2', 'task3'] as Set
+    }
+
+    def dependenciesIncludeExternalTasks() {
+        def task1 = task('task1')
+        def task2 = task('task2', 'other')
+        def task3 = task('task3', 'group', task1, task2)
+
+        when:
+        model.build([task2, task3])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group') as List).first()
+        t.dependencies*.path*.name as Set == ['task1', 'task2'] as Set
+    }
+
+    def dependenciesDoNotIncludeTheChildrenOfOtherTopLevelTasks() {
+        def task1 = task('task1')
+        def task2 = task('task2', 'group1', task1)
+        def task3 = task('task3', task1)
+        def task4 = task('task4', task2)
+        def task5 = task('task5', 'group2', task3, task4)
+
+        when:
+        model.build([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group2') as List).first()
+        t.dependencies*.path*.name as Set == ['task2'] as Set
+    }
+
+    def addsAGroupThatContainsTheTasksWithNoGroup() {
+        def task1 = task('task1')
+        def task2 = task('task2', 'group', task1)
+        def task3 = task('task3')
+        def task4 = task('task4', task2)
+        def task5 = task('task5', task3, task4)
+
+        when:
+        model.build([task1, task2, task3, task4, task5])
+
+        then:
+        model.groups == ['group', ''] as Set
+        def tasks = model.getTasksForGroup('') as List
+        tasks*.task == [task5]
+        def t = tasks.first()
+        t.task == task5
+        t.children*.task == [task3, task4]
+    }
+
+    def addsAGroupWhenThereAreNoTasksWithAGroup() {
+        def task1 = task('task1')
+        def task2 = task('task2', task1)
+        def task3 = task('task3')
+
+        when:
+        model.build([task1, task2, task3])
+
+        then:
+        model.groups == [''] as Set
+        def tasks = model.getTasksForGroup('') as List
+        tasks*.task == [task2, task3]
+    }
+
+    def buildsModelWhenThereAreNoTasks() {
+        when:
+        model.build([])
+
+        then:
+        model.groups as List == []
+    }
+
+    def ignoresReachableTasksOutsideTheProject() {
+        def other1 = task('other1')
+        def other2 = task('other2', other1)
+        def task1 = task('task1', other2)
+        def task2 = task('task2', 'group1', task1)
+
+        when:
+        model.build([task1, task2])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group1') as List).first()
+        t.children*.task == [task1]
+        t.dependencies*.path*.name == ['other2']
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy
new file mode 100644
index 0000000..f8d69b4
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.diagnostics.internal
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.util.Path
+
+class TaskDetailsFactoryTest extends AbstractTaskModelSpec {
+    final Project project = Mock()
+    final Project subproject = Mock()
+    final Task task = Mock()
+    TaskDetailsFactory factory
+
+    def setup() {
+        project.allprojects >> [project, subproject]
+        factory = new TaskDetailsFactory(project)
+    }
+    
+    def createsDetailsForTaskInMainProject() {
+        task.project >> project
+        task.path >> ':path'
+        project.relativeProjectPath(':path') >> 'task'
+
+        expect:
+        def details = factory.create(task)
+        details.path == Path.path('task')
+    }
+
+    def createsDetailsForTaskInSubProject() {
+        task.project >> subproject
+        task.path >> ':sub:path'
+        project.relativeProjectPath(':sub:path') >> 'sub:task'
+
+        expect:
+        def details = factory.create(task)
+        details.path == Path.path('sub:task')
+    }
+
+    def createsDetailsForTaskInOtherProject() {
+        Project other = Mock()
+        task.project >> other
+        task.path >> ':other:task'
+
+        expect:
+        def details = factory.create(task)
+        details.path == Path.path(':other:task')
+    }
+
+    def providesValuesForOtherProperties() {
+        task.project >> project
+        task.name >> 'task'
+        task.description >> 'description'
+
+        expect:
+        def details = factory.create(task)
+        details.description == 'description'
+        details.dependencies.isEmpty()
+        details.children.isEmpty()
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
new file mode 100644
index 0000000..7817b99
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Rule
+import org.gradle.logging.TestStyledTextOutput
+
+/**
+ * @author Hans Dockter
+ */
+class TaskReportRendererTest extends AbstractTaskModelSpec {
+    private final TestStyledTextOutput writer = new TestStyledTextOutput().ignoreStyle()
+    private final TaskReportRenderer renderer = new TaskReportRenderer()
+
+    def setup() {
+        renderer.output = writer
+    }
+
+    def writesTaskAndDependenciesWithDetailDisabled() {
+        TaskDetails task1 = taskDetails(':task1', description: 'task1Description')
+        TaskDetails task2 = taskDetails(':task2')
+        TaskDetails task3 = taskDetails(':task3')
+        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
+        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
+
+        List testDefaultTasks = ['task1', 'task2']
+
+        when:
+        renderer.showDetail(false)
+        renderer.addDefaultTasks(testDefaultTasks)
+        renderer.startTaskGroup('group')
+        renderer.addTask(task1)
+        renderer.addChildTask(task2)
+        renderer.addTask(task3)
+        renderer.completeTasks()
+        renderer.addRule(rule1)
+        renderer.addRule(rule2)
+
+        then:
+        writer.value == '''Default tasks: task1, task2
+
+Group tasks
+-----------
+:task1 - task1Description
+:task3
+
+Rules
+-----
+rule1Description
+rule2Description
+'''
+    }
+
+    def writesTaskAndDependenciesWithDetail() {
+        TaskDetails task11 = taskDetails(':task11')
+        TaskDetails task12 = taskDetails(':task12')
+        TaskDetails task1 = taskDetails(':task1', description: 'task1Description', dependencies: [task11, task12])
+        TaskDetails task2 = taskDetails(':task2')
+        TaskDetails task3 = taskDetails(':task3', dependencies: [task1])
+        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
+        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
+        List testDefaultTasks = ['task1', 'task2']
+
+        when:
+        renderer.showDetail(true)
+        renderer.addDefaultTasks(testDefaultTasks)
+        renderer.startTaskGroup('group')
+        renderer.addTask(task1)
+        renderer.addChildTask(task2)
+        renderer.addTask(task3)
+        renderer.completeTasks()
+        renderer.addRule(rule1)
+        renderer.addRule(rule2)
+
+        then:
+        writer.value == '''Default tasks: task1, task2
+
+Group tasks
+-----------
+:task1 - task1Description [:task11, :task12]
+    :task2
+:task3 [:task1]
+
+Rules
+-----
+rule1Description
+rule2Description
+'''
+    }
+
+    def writesTasksForSingleGroup() {
+        TaskDetails task = taskDetails(':task1')
+
+        when:
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('group')
+        renderer.addTask(task)
+        renderer.completeTasks()
+
+        then:
+        writer.value == '''Group tasks
+-----------
+:task1
+'''
+    }
+
+    def writesTasksForMultipleGroups() {
+        TaskDetails task = taskDetails(':task1')
+        TaskDetails task2 = taskDetails(':task2')
+
+        when:
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('group')
+        renderer.addTask(task)
+        renderer.startTaskGroup('other')
+        renderer.addTask(task2)
+        renderer.completeTasks()
+
+        then:
+        writer.value == '''Group tasks
+-----------
+:task1
+
+Other tasks
+-----------
+:task2
+'''
+    }
+
+    def writesTasksForDefaultGroup() {
+        TaskDetails task = taskDetails(':task1')
+
+        when:
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('')
+        renderer.addTask(task)
+        renderer.completeTasks()
+
+        then:
+        writer.value == '''Tasks
+-----
+:task1
+'''
+    }
+
+    def writesProjectWithNoTasksAndNoRules() {
+        when:
+        renderer.completeTasks()
+
+        then:
+        writer.value == '''No tasks
+'''
+    }
+
+    def writesProjectWithRulesAndNoTasks() {
+        String ruleDescription = "someDescription"
+
+        when:
+        renderer.completeTasks()
+        renderer.addRule([getDescription: {ruleDescription}] as Rule)
+
+        then:
+        writer.value == '''No tasks
+
+Rules
+-----
+someDescription
+'''
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
new file mode 100644
index 0000000..12f4f23
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.TestStyledTextOutput
+import org.gradle.logging.internal.StreamingStyledTextOutput
+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/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy
new file mode 100644
index 0000000..ffb59ac
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dependencies/AsciiDependencyReportRendererTest.groovy
@@ -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.tasks.diagnostics.internal.dependencies
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.tasks.diagnostics.internal.graph.DependencyGraphRenderer
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.SimpleDependency
+import org.gradle.logging.TestStyledTextOutput
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class AsciiDependencyReportRendererTest extends Specification {
+    private final TestStyledTextOutput textOutput = new TestStyledTextOutput().ignoreStyle()
+    private final AsciiDependencyReportRenderer renderer = new AsciiDependencyReportRenderer()
+    private final Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        renderer.output = textOutput
+    }
+
+    def "informs if no configurations"() {
+        when:
+        renderer.startProject(project);
+        renderer.completeProject(project);
+
+        then:
+        textOutput.value.contains('No configurations')
+    }
+
+    def "shows configuration header"() {
+        Configuration configuration1 = Mock()
+        configuration1.getName() >> 'config1'
+        configuration1.getDescription() >> 'description'
+        Configuration configuration2 = Mock()
+        configuration2.getName() >> 'config2'
+
+        when:
+        renderer.startConfiguration(configuration1);
+        renderer.completeConfiguration(configuration1);
+        renderer.startConfiguration(configuration2);
+        renderer.completeConfiguration(configuration2);
+
+        then:
+        textOutput.value.readLines() == [
+                'config1 - description',
+                '',
+                'config2'
+        ]
+    }
+
+    def "renders dependency graph"() {
+        renderer.dependencyGraphRenderer = Mock(DependencyGraphRenderer)
+        def root = new SimpleDependency("root")
+        root.children.add(new SimpleDependency("dep"))
+
+        when:
+        renderer.renderNow(root)
+
+        then:
+        1 * renderer.dependencyGraphRenderer.render(root)
+    }
+
+    def "renders legend on complete"() {
+        renderer.dependencyGraphRenderer = Mock(DependencyGraphRenderer)
+
+        when:
+        renderer.complete()
+
+        then:
+        1 * renderer.dependencyGraphRenderer.printLegend()
+    }
+
+    def "safely completes if no configurations"() {
+        when:
+        //no configuration started, and:
+        renderer.complete()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "informs if no dependencies"() {
+        def root = new SimpleDependency("root", "config")
+
+        when:
+        renderer.renderNow(root)
+
+        then:
+        textOutput.value.readLines() == ['No dependencies']
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParserSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParserSpec.groovy
new file mode 100644
index 0000000..9ba513a
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecNotationParserSpec.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.dsl
+
+import org.gradle.api.artifacts.result.DependencyResult
+import org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.specs.Spec
+import spock.lang.Specification
+import org.gradle.api.InvalidUserDataException
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+class DependencyResultSpecNotationParserSpec extends Specification {
+
+    NotationParser<Spec<DependencyResult>> parser = DependencyResultSpecNotationParser.create()
+
+    def "accepts closures"() {
+        given:
+        def mockito = ResolutionResultDataBuilder.newDependency('org.mockito', 'mockito-core')
+        def other = ResolutionResultDataBuilder.newDependency('org.mockito', 'other')
+
+        when:
+        def spec = parser.parseNotation( { it.requested.name == 'mockito-core' } )
+
+        then:
+        spec.isSatisfiedBy(mockito)
+        !spec.isSatisfiedBy(other)
+    }
+
+    def "accepts Strings"() {
+        given:
+        def mockito = ResolutionResultDataBuilder.newDependency('org.mockito', 'mockito-core')
+        def other = ResolutionResultDataBuilder.newDependency('org.mockito', 'other')
+
+        when:
+        def spec = parser.parseNotation('mockito-core')
+
+        then:
+        spec.isSatisfiedBy(mockito)
+        !spec.isSatisfiedBy(other)
+    }
+
+    def "accepts specs"() {
+        given:
+        def mockito = ResolutionResultDataBuilder.newDependency('org.mockito', 'mockito-core')
+        def other = ResolutionResultDataBuilder.newDependency('org.mockito', 'other')
+
+        when:
+        def spec = parser.parseNotation(new Spec<DependencyResult>() {
+            boolean isSatisfiedBy(DependencyResult element) {
+                return element.getRequested().getName().equals('mockito-core')
+            }
+        })
+
+        then:
+        spec.isSatisfiedBy(mockito)
+        !spec.isSatisfiedBy(other)
+    }
+
+    def "fails neatly for unknown notations"() {
+        when:
+        parser.parseNotation(['not supported'])
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains 'not supported'
+        ex.message.contains 'DependencyInsight.dependency'
+    }
+
+    def "does not accept empty Strings"() {
+        when:
+        parser.parseNotation('')
+        then:
+        thrown(InvalidUserDataException)
+
+        when:
+        parser.parseNotation(' ')
+        then:
+        thrown(InvalidUserDataException)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecTest.groovy
new file mode 100644
index 0000000..ea241fc
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/dsl/DependencyResultSpecTest.groovy
@@ -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.tasks.diagnostics.internal.dsl
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newDependency
+
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newUnresolvedDependency
+
+/**
+ * by Szczepan Faber, created at: 11/6/12
+ */
+class DependencyResultSpecTest extends Specification {
+
+    def "knows matching dependencies"() {
+        expect:
+        new DependencyResultSpec(notation).isSatisfiedBy(newDependency("org.foo", "foo-core", "1.0"))
+
+        where:
+        notation   << ['org', 'org.foo', 'foo-core', '1.0', 'org.foo:foo-core', 'org.foo:foo-core:1.0']
+    }
+
+    def "knows mismatching dependencies"() {
+        expect:
+        !new DependencyResultSpec(notation).isSatisfiedBy(newDependency("org.foo", "foo-core", "1.0"))
+
+        where:
+        notation << ['org1', '2.0', 'core-foo']
+    }
+
+    def "matches unresolved dependencies"() {
+        expect:
+        new DependencyResultSpec(notation).isSatisfiedBy(newUnresolvedDependency("org.foo", "foo-core", "5.0"))
+
+        where:
+        notation << ['core', 'foo-core', 'foo-core:5.0', 'org.foo:foo-core:5.0', '5.0']
+    }
+
+    def "does not match unresolved dependencies"() {
+        expect:
+        !new DependencyResultSpec(notation).isSatisfiedBy(newUnresolvedDependency("org.foo", "foo-core", "5.0"))
+
+        where:
+        notation << ['xxx', '4.0']
+    }
+
+    def "matches by selected module or requested dependency"() {
+        expect:
+        new DependencyResultSpec(notation).isSatisfiedBy(newDependency("org.foo", "foo-core", "1.+", "1.22"))
+
+        where:
+        notation << ['1.+', '1.22', 'foo-core:1.+', 'foo-core:1.22', 'org.foo:foo-core:1.+', 'org.foo:foo-core:1.22']
+    }
+}
\ No newline at end of file
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRendererSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRendererSpec.groovy
new file mode 100644
index 0000000..9820b2b
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/DependencyGraphRendererSpec.groovy
@@ -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.tasks.diagnostics.internal.graph
+
+import org.gradle.api.tasks.diagnostics.internal.GraphRenderer
+import org.gradle.api.tasks.diagnostics.internal.graph.nodes.SimpleDependency
+import org.gradle.logging.TestStyledTextOutput
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 9/21/12
+ */
+class DependencyGraphRendererSpec extends Specification {
+
+    private textOutput = new TestStyledTextOutput().ignoreStyle()
+    private graphRenderer = new GraphRenderer(textOutput)
+    private renderer = new DependencyGraphRenderer(graphRenderer, new SimpleNodeRenderer())
+
+    def "renders graph"() {
+        def root = new SimpleDependency("root")
+        def dep1 = new SimpleDependency("dep1")
+        def dep11 = new SimpleDependency("dep1.1")
+        def dep2 = new SimpleDependency("dep2")
+        def dep21 = new SimpleDependency("dep2.1")
+        def dep22 = new SimpleDependency("dep2.2")
+
+        root.children.addAll(dep1, dep2)
+        dep1.children.addAll(dep11)
+        dep2.children.addAll(dep21, dep22)
+
+        when:
+        renderer.render(root)
+        renderer.printLegend()
+
+        then:
+        textOutput.value.readLines() == [
+                '+--- dep1',
+                '|    \\--- dep1.1',
+                '\\--- dep2',
+                '     +--- dep2.1',
+                '     \\--- dep2.2'
+        ]
+    }
+
+    def "renders graph with repeated nodes"() {
+        def root = new SimpleDependency("root")
+        def dep1 = new SimpleDependency("dep1")
+        def dep11 = new SimpleDependency("dep1.1")
+        def dep2 = new SimpleDependency("dep2")
+        def dep22 = new SimpleDependency("dep2.2")
+
+        root.children.addAll(dep1, dep2)
+        dep1.children.addAll(dep11)
+        dep2.children.addAll(dep1, dep22)
+
+        when:
+        renderer.render(root)
+        renderer.printLegend()
+
+        then:
+        textOutput.value.readLines() == [
+                '+--- dep1',
+                '|    \\--- dep1.1',
+                '\\--- dep2',
+                '     +--- dep1 (*)',
+                '     \\--- dep2.2',
+                '',
+                '(*) - dependencies omitted (listed previously)'
+        ]
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
new file mode 100644
index 0000000..6cad400
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.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.api.tasks.diagnostics.internal.graph.nodes
+
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
+import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+
+/**
+ * by Szczepan Faber, created at: 10/9/12
+ */
+class AbstractRenderableDependencyResultSpec extends Specification {
+
+    def "renders name cleanly"() {
+        given:
+        def requested = newSelector('org.mockito', 'mockito-core', '1.0')
+
+        expect:
+        dep(requested, newModule('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
+        dep(requested, newModule('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
+        dep(requested, newModule('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, newModule('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
+    }
+
+    private RenderableDependency dep(ModuleVersionSelector requested, ResolvedModuleVersionResult selected) {
+        Spy(AbstractRenderableDependencyResult, constructorArgs: [new DefaultResolvedDependencyResult(requested, selected, newModule()), null])
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.groovy
new file mode 100644
index 0000000..2d437c7
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/InvertedRenderableDependencyResultTest.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.api.tasks.diagnostics.internal.graph.nodes
+
+import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
+import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
+
+/**
+ * by Szczepan Faber, created at: 9/21/12
+ */
+class InvertedRenderableDependencyResultTest extends Specification {
+
+    def "uses dependents as children"() {
+        expect:
+        /*
+        //at some point, find a better way to construct test data (graphs)
+        root->y->b(1.0)
+        root->x->b(1.0)
+        root->z->b(0.5)
+        */
+
+        def root = newModule("root")
+
+        def x = newDependency("org", "x", "1.0", "1.0", root)
+        def y = newDependency("org", "y", "1.0", "1.0", root)
+        def z = newDependency("org", "z", "1.0", "1.0", root)
+
+        def selectedB = newModule("org", "b", "1.0")
+
+        def b1 = newDependency("org", "b", "1.0", "1.0", x.selected, selectedB)
+        def b2 = newDependency("org", "b", "1.0", "1.0", y.selected, selectedB)
+        def b3 = newDependency("org", "b", "1.0", "0.5", z.selected, selectedB)
+
+        when:
+        def result = new InvertedRenderableDependencyResult(b3, null)
+
+        then:
+        result.children*.name == ['org:z:1.0']
+
+        when:
+        result = new InvertedRenderableDependencyResult(b2, null)
+
+        then:
+        result.children*.name == ['org:x:1.0', 'org:y:1.0']
+    }
+
+    static DefaultResolvedDependencyResult newDependency(String group='a', String module='a', String version='1', String requested = version,
+                                                         DefaultResolvedModuleVersionResult from = newModule(),
+                                                         DefaultResolvedModuleVersionResult selected = newModule(group, module, version)) {
+        def out = new DefaultResolvedDependencyResult(newSelector(group, module, requested), selected, from)
+        selected.addDependent(out)
+        out
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporterSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporterSpec.groovy
new file mode 100644
index 0000000..9a37f3a
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/DependencyInsightReporterSpec.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.insight
+
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
+import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.CONFLICT_RESOLUTION
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.FORCED
+
+/**
+ * Created: 23/08/2012
+ * @author Szczepan Faber
+ */
+class DependencyInsightReporterSpec extends Specification {
+
+    def "sorts dependencies"() {
+        def dependencies = [dep("a", "x", "1.0", "2.0"), dep("a", "x", "1.5", "2.0"), dep("b", "a", "5.0"), dep("a", "z", "1.0"), dep("a", "x", "2.0")]
+
+        when:
+        def sorted = new DependencyInsightReporter().prepare(dependencies);
+
+        then:
+        sorted.size() == 5
+
+        sorted[0].name == 'a:x:2.0'
+        !sorted[0].description
+
+        sorted[1].name == 'a:x:1.0 -> 2.0'
+        !sorted[1].description
+
+        sorted[2].name == 'a:x:1.5 -> 2.0'
+        !sorted[2].description
+
+        sorted[3].name == 'a:z:1.0'
+        !sorted[3].description
+
+        sorted[4].name == 'b:a:5.0'
+        !sorted[4].description
+    }
+
+    def "adds header dependency if the selected version does not exist in the graph"() {
+        def dependencies = [dep("a", "x", "1.0", "2.0", FORCED), dep("a", "x", "1.5", "2.0", FORCED), dep("b", "a", "5.0")]
+
+        when:
+        def sorted = new DependencyInsightReporter().prepare(dependencies);
+
+        then:
+        sorted.size() == 4
+
+        sorted[0].name == 'a:x:2.0'
+        sorted[0].description == 'forced'
+
+        sorted[1].name == 'a:x:1.0 -> 2.0'
+        !sorted[1].description
+
+        sorted[2].name == 'a:x:1.5 -> 2.0'
+        !sorted[2].description
+
+        sorted[3].name == 'b:a:5.0'
+        !sorted[3].description
+    }
+
+    def "annotates only first dependency in the group"() {
+        def dependencies = [dep("a", "x", "1.0", "2.0", CONFLICT_RESOLUTION), dep("a", "x", "2.0", "2.0", CONFLICT_RESOLUTION), dep("b", "a", "5.0", "5.0", FORCED)]
+
+        when:
+        def sorted = new DependencyInsightReporter().prepare(dependencies);
+
+        then:
+        sorted.size() == 3
+
+        sorted[0].name == 'a:x:2.0'
+        sorted[0].description == 'conflict resolution'
+
+        sorted[1].name == 'a:x:1.0 -> 2.0'
+        !sorted[1].description
+
+        sorted[2].name == 'b:a:5.0'
+        sorted[2].description == 'forced'
+    }
+
+    private dep(String group, String name, String requested, String selected = requested, ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED) {
+        def selectedModule = new DefaultResolvedModuleVersionResult(newId(group, name, selected), selectionReason)
+        new DefaultResolvedDependencyResult(newSelector(group, name, requested),
+                selectedModule,
+                new DefaultResolvedModuleVersionResult(newId("a", "root", "1")))
+    }
+}
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.groovy
new file mode 100644
index 0000000..a2da8fd
--- /dev/null
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/insight/ResolvedDependencyResultSorterSpec.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.api.tasks.diagnostics.internal.insight
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.internal.artifacts.result.DefaultResolvedDependencyResult
+import org.gradle.api.internal.artifacts.result.DefaultResolvedModuleVersionResult
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 8/22/12
+ */
+class ResolvedDependencyResultSorterSpec extends Specification {
+
+    def "sorts"() {
+        def d1 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
+        def d3 = newDependency(newSelector("org.gradle", "core", "1.5"), newId("org.gradle", "core", "2.0"))
+
+        def d4 = newDependency(newSelector("org.gradle", "xxxx", "1.0"), newId("org.gradle", "xxxx", "1.0"))
+
+        def d5 = newDependency(newSelector("org.gradle", "zzzz", "1.5"), newId("org.gradle", "zzzz", "3.0"))
+        def d6 = newDependency(newSelector("org.gradle", "zzzz", "2.0"), newId("org.gradle", "zzzz", "3.0"))
+
+        def d7 = newDependency(newSelector("org.aha", "aha", "1.0"), newId("org.gradle", "zzzz", "3.0"))
+
+        when:
+        def sorted = ResolvedDependencyResultSorter.sort([d5, d3, d6, d1, d2, d7, d4])
+
+        then:
+        sorted == [d7, d1, d2, d3, d4, d5, d6]
+    }
+
+    def "semantically compares versions"() {
+        def d1 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"))
+        def d2 = newDependency(newSelector("org.gradle", "core", "1.0-alpha"), newId("org.gradle", "core", "2.0"))
+
+        when:
+        def sorted = ResolvedDependencyResultSorter.sort([d1, d2])
+
+        then:
+        sorted == [d2, d1]
+    }
+
+    def "excludes dependencies with the same requested->selected"() {
+        def d1 = newDependency(newSelector("org.gradle", "core", "2.0"), newId("org.gradle", "core", "2.0"), "foo")
+        def d2 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), "bar")
+        def d3 = newDependency(newSelector("org.gradle", "core", "1.0"), newId("org.gradle", "core", "2.0"), "baz")
+
+        when:
+        def sorted = ResolvedDependencyResultSorter.sort([d3, d2, d1])
+
+        then:
+        sorted == [d1, d3]
+    }
+
+    private newDependency(ModuleVersionSelector requested, ModuleVersionIdentifier selected, String from = 'whatever') {
+        new DefaultResolvedDependencyResult(requested, new DefaultResolvedModuleVersionResult(selected),
+                new DefaultResolvedModuleVersionResult(newId("org", from, "1.0")))
+    }
+}
diff --git a/subprojects/distributions/distributions.gradle b/subprojects/distributions/distributions.gradle
new file mode 100644
index 0000000..37224a4
--- /dev/null
+++ b/subprojects/distributions/distributions.gradle
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 is a groovy project because we have int tests.
+// Remove any pre-configured archives
+configurations.all {
+    artifacts.clear()
+}
+tasks.remove(jar)
+
+tasks.withType(AbstractArchiveTask) {
+    baseName "gradle"
+
+    // The CI server looks for the distributions at this location
+    destinationDir = rootProject.distsDir
+    clean.delete archivePath
+}
+
+dependencies {
+    groovy libraries.groovy
+}
+
+configurations {
+    dists
+}
+
+integTestTasks.all {
+    if (project.hasProperty("noDistTests")) {
+        enabled = false
+    } else {
+        inputs.files buildDists
+    }
+}
+
+daemonIntegTest.enabled = false
+
+evaluationDependsOn ":docs"
+
+ext {
+    zipRootFolder = "gradle-$version"
+
+    binDistImage = copySpec {
+        from('src/toplevel') {
+            exclude 'media/**'
+            expand(version: version)
+        }
+        from('src/toplevel') {
+            include 'media/**'
+        }
+        from project(':docs').outputs.distDocs
+        into('bin') {
+            from { project(':launcher').startScripts.outputs.files }
+            fileMode = 0755
+        }
+        into('lib') {
+            from rootProject.configurations.runtime
+            into('plugins') {
+                from rootProject.configurations.plugins - rootProject.configurations.runtime
+            }
+        }
+    }
+
+    allDistImage = copySpec {
+        with binDistImage
+        into('src') {
+            from groovyProjects.collect {project -> project.sourceSets.main.allSource }
+        }
+        into('docs') {
+            from project(':docs').outputs.docs
+        }
+        into('samples') {
+            from project(':docs').outputs.samples
+        }
+    }
+}
+
+task allZip(type: Zip) {
+    classifier = 'all'
+    into(zipRootFolder) {
+        with allDistImage
+    }
+}
+
+task binZip(type: Zip) {
+    classifier = 'bin'
+    into(zipRootFolder) {
+        with binDistImage
+    }
+}
+
+task srcZip(type: Zip) {
+    classifier = 'src'
+    into(zipRootFolder) {
+        from(rootProject.file('gradlew')) {
+            fileMode = 0755
+        }
+        from(rootProject.projectDir) {
+            def spec = delegate
+            ['buildSrc', 'subprojects/*'].each {
+                spec.include "$it/*.gradle"
+                spec.include "$it/src/"
+            }
+            include 'config/'
+            include 'gradle/'
+            include 'src/'
+            include '*.gradle'
+            include 'wrapper/'
+            include 'gradlew.bat'
+        }
+    }
+}
+
+task outputsZip(type: Zip) {
+    archiveName "outputs.zip"
+    from rootProject.createBuildReceipt
+    ["all", "bin", "src"].each { from(tasks["${it}Zip"]) }
+}
+
+artifacts {
+    dists allZip, binZip, srcZip
+}
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.groovy
new file mode 100644
index 0000000..2fe889f
--- /dev/null
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/AllDistributionIntegrationSpec.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
+
+import groovy.io.FileType
+import org.gradle.util.TestFile
+
+import static org.hamcrest.Matchers.containsString
+
+class AllDistributionIntegrationSpec extends DistributionIntegrationSpec {
+
+    def allZipContents() {
+        given:
+        TestFile contentsDir = unpackDistribution("all")
+
+        expect:
+        checkMinimalContents(contentsDir)
+
+        // Source
+        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/WrapperExecutor.java').assertIsFile()
+
+        // Samples
+        contentsDir.file('samples/java/quickstart/build.gradle').assertIsFile()
+
+        def buildAndGradleDirs = []
+        contentsDir.file('samples').eachFileRecurse(FileType.DIRECTORIES) {
+            if (it.name == "build" || it.name == ".gradle") {
+                buildAndGradleDirs << it
+            }
+        }
+        buildAndGradleDirs.empty
+
+        // Javadoc
+        contentsDir.file('docs/javadoc/index.html').assertIsFile()
+        contentsDir.file('docs/javadoc/index.html').assertContents(containsString("Gradle API ${version}"))
+        contentsDir.file('docs/javadoc/org/gradle/api/Project.html').assertIsFile()
+
+        // Groovydoc
+        contentsDir.file('docs/groovydoc/index.html').assertIsFile()
+        contentsDir.file('docs/groovydoc/org/gradle/api/Project.html').assertIsFile()
+        contentsDir.file('docs/groovydoc/org/gradle/api/tasks/bundling/Zip.html').assertIsFile()
+
+        // Userguide
+        contentsDir.file('docs/userguide/userguide.html').assertIsFile()
+        contentsDir.file('docs/userguide/userguide.html').assertContents(containsString("<h3 class=\"releaseinfo\">Version ${version}</h3>"))
+        contentsDir.file('docs/userguide/userguide_single.html').assertIsFile()
+        contentsDir.file('docs/userguide/userguide_single.html').assertContents(containsString("<h3 class=\"releaseinfo\">Version ${version}</h3>"))
+//        contentsDir.file('docs/userguide/userguide.pdf').assertIsFile()
+
+        // DSL reference
+        contentsDir.file('docs/dsl/index.html').assertIsFile()
+        contentsDir.file('docs/dsl/index.html').assertContents(containsString("<title>Gradle DSL Version ${version}</title>"))
+    }
+
+}
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.groovy
new file mode 100644
index 0000000..ec59b7d
--- /dev/null
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/BinDistributionIntegrationSpec.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
+
+import org.gradle.util.TestFile
+
+class BinDistributionIntegrationSpec extends DistributionIntegrationSpec {
+
+    def binZipContents() {
+        given:
+        TestFile contentsDir = unpackDistribution("bin")
+
+        expect:
+        checkMinimalContents(contentsDir)
+        contentsDir.file('src').assertDoesNotExist()
+        contentsDir.file('samples').assertDoesNotExist()
+        contentsDir.file('docs').assertDoesNotExist()
+    }
+
+}
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
new file mode 100644
index 0000000..3522b4a
--- /dev/null
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy
@@ -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
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.GradleVersion
+import org.gradle.util.PreconditionVerifier
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Shared
+import spock.lang.Specification
+
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+
+class DistributionIntegrationSpec extends Specification {
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final PreconditionVerifier preconditionVerifier = new PreconditionVerifier()
+
+    @Shared String version = GradleVersion.current().version
+
+    protected TestFile unpackDistribution(type) {
+        TestFile srcZip = dist.distributionsDir.file("gradle-$version-${type}.zip")
+        srcZip.usingNativeTools().unzipTo(dist.testDir)
+        TestFile contentsDir = dist.testDir.file("gradle-$version")
+        contentsDir
+    }
+
+    protected void checkMinimalContents(TestFile contentsDir) {
+        // Check it can be executed
+        executer.inDirectory(contentsDir).usingExecutable('bin/gradle').withTaskList().run()
+
+        // Scripts
+        contentsDir.file('bin/gradle').assertIsFile()
+        contentsDir.file('bin/gradle.bat').assertIsFile()
+
+        // Top level files
+        contentsDir.file('LICENSE').assertIsFile()
+
+        // Core libs
+        def coreLibs = contentsDir.file("lib").listFiles().findAll { it.name.startsWith("gradle-") }
+        assert coreLibs.size() == 11
+        coreLibs.each { assertIsGradleJar(it) }
+        def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
+        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"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-code-quality-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-antlr-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-announce-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-jetty-${version}.jar"))
+        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()
+    }
+
+    protected void assertIsGradleJar(TestFile jar) {
+        jar.assertIsFile()
+        assertThat(jar.manifest.mainAttributes.getValue('Implementation-Version'), equalTo(version))
+        assertThat(jar.manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle'))
+    }
+}
diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.groovy
new file mode 100644
index 0000000..5531792
--- /dev/null
+++ b/subprojects/distributions/src/integTest/groovy/org/gradle/SrcDistributionIntegrationSpec.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
+
+import org.apache.tools.ant.taskdefs.Expand
+import org.gradle.util.AntUtil
+import org.gradle.util.Requires
+import org.gradle.util.TestFile
+import org.gradle.util.TestPrecondition
+
+class SrcDistributionIntegrationSpec extends DistributionIntegrationSpec {
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def sourceZipContents() {
+        given:
+        TestFile contentsDir = unpackDistribution("src")
+
+        expect:
+        !contentsDir.file(".git").exists()
+
+        when:
+        executer.with {
+            withDeprecationChecksDisabled()
+            inDirectory(contentsDir)
+            usingExecutable('gradlew')
+            withTasks('binZip')
+        }.run()
+
+        then:
+        File binZip = contentsDir.file("build/distributions").listFiles().find() { it.name.endsWith("-bin.zip") }
+        binZip.exists()
+
+        when:
+        Expand unpack = new Expand()
+        unpack.src = binZip
+        unpack.dest = contentsDir.file('build/distributions/unzip')
+        AntUtil.execute(unpack)
+
+        then:
+        TestFile unpackedRoot = new TestFile(contentsDir.file('build/distributions/unzip').listFiles().first())
+        unpackedRoot.file("bin/gradle").exists()
+    }
+
+}
diff --git a/src/toplevel/LICENSE b/subprojects/distributions/src/toplevel/LICENSE
similarity index 100%
rename from src/toplevel/LICENSE
rename to subprojects/distributions/src/toplevel/LICENSE
diff --git a/src/toplevel/NOTICE b/subprojects/distributions/src/toplevel/NOTICE
similarity index 100%
rename from src/toplevel/NOTICE
rename to subprojects/distributions/src/toplevel/NOTICE
diff --git a/src/toplevel/changelog.txt b/subprojects/distributions/src/toplevel/changelog.txt
similarity index 100%
rename from src/toplevel/changelog.txt
rename to subprojects/distributions/src/toplevel/changelog.txt
diff --git a/src/toplevel/init.d/readme.txt b/subprojects/distributions/src/toplevel/init.d/readme.txt
similarity index 100%
rename from src/toplevel/init.d/readme.txt
rename to subprojects/distributions/src/toplevel/init.d/readme.txt
diff --git a/src/toplevel/media/gradle-icon-128x128.png b/subprojects/distributions/src/toplevel/media/gradle-icon-128x128.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-128x128.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-128x128.png
diff --git a/src/toplevel/media/gradle-icon-16x16.png b/subprojects/distributions/src/toplevel/media/gradle-icon-16x16.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-16x16.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-16x16.png
diff --git a/src/toplevel/media/gradle-icon-24x24.png b/subprojects/distributions/src/toplevel/media/gradle-icon-24x24.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-24x24.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-24x24.png
diff --git a/src/toplevel/media/gradle-icon-256x256.png b/subprojects/distributions/src/toplevel/media/gradle-icon-256x256.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-256x256.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-256x256.png
diff --git a/src/toplevel/media/gradle-icon-32x32.png b/subprojects/distributions/src/toplevel/media/gradle-icon-32x32.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-32x32.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-32x32.png
diff --git a/src/toplevel/media/gradle-icon-48x48.png b/subprojects/distributions/src/toplevel/media/gradle-icon-48x48.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-48x48.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-48x48.png
diff --git a/src/toplevel/media/gradle-icon-512x512.png b/subprojects/distributions/src/toplevel/media/gradle-icon-512x512.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-512x512.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-512x512.png
diff --git a/src/toplevel/media/gradle-icon-64x64.png b/subprojects/distributions/src/toplevel/media/gradle-icon-64x64.png
similarity index 100%
rename from src/toplevel/media/gradle-icon-64x64.png
rename to subprojects/distributions/src/toplevel/media/gradle-icon-64x64.png
diff --git a/src/toplevel/media/gradle.icns b/subprojects/distributions/src/toplevel/media/gradle.icns
similarity index 100%
rename from src/toplevel/media/gradle.icns
rename to subprojects/distributions/src/toplevel/media/gradle.icns
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index 25c660a..56be49a 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -19,16 +19,31 @@ 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.internal.os.OperatingSystem
+import org.gradle.build.docs.dsl.source.ExtractDslMetaDataTask
 
 apply plugin: 'base'
 apply plugin: 'pegdown'
 apply plugin: 'jsoup'
+apply plugin: 'javascript-base'
+
+repositories {
+    add javaScript.googleApisRepository
+
+    ivy {
+        name "Google Fonts"
+        url "http://themes.googleusercontent.com/static/fonts/"
+        layout 'pattern', {
+            artifact '[organisation]/v[revision]/[classifier](.[ext])'
+            ivy '[organisation]/v[revision]/ivy(.[ext])'
+        }
+    }
+}
 
 configurations {
     userGuideStyleSheets
     userGuideTask
+    jquery
+    fonts
 }
 
 dependencies {
@@ -39,12 +54,22 @@ dependencies {
     userGuideTask 'xslthl:xslthl:2.0.1 at jar'
 
     userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
+    jquery "jquery:jquery.min:1.8.0 at js"
+
+    fonts \
+        "lato:regular:6:v0SdcGFAl2aezM9Vq_aFTQ at ttf",
+        "lato:regular-italic:6:LqowQDslGv4DmUBAfWa2Vw at ttf",
+        "lato:bold:6:DvlFBScY1r-FMtZSYIYoYw at ttf",
+        "lato:bold-italic:6:HkF_qI1x_noxlxhrhMQYEKCWcynf_cDxXwCLxiixG1c at ttf",
+        "ubuntumono:regular:3:ViZhet7Ak-LRXZMXzuAfkZ0EAVxt0G0biEntp43Qt6E at ttf",
+        "ubuntumono:regular-italic:3:KAKuHXAHZOeECOWAHsRKA-LrC4Du4e_yfTJ8Ol60xk0 at ttf",
+        "ubuntumono:bold:3:ceqTZGKHipo8pJj4molytp_TkvowlIOtbR7ePgFOpF4 at ttf",
+        "ubuntumono:bold-italic:3:n_d8tv_JOIiYyMXR4eaV9WsGzsqhEorxQDpu60nfWEc at ttf"
 }
 
 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')
@@ -75,7 +100,7 @@ tasks.withType(UserGuideTransformTask) {
     snippetsDir = samples.snippetsDir
     linksFile = dslDocbook.linksFile
     websiteUrl = 'http://www.gradle.org'
-    
+
     if (name in ["pdfUserguideDocbook", "userguideFragmentSrc"]) {
         // These will only be valid for releases, but that's ok
         javadocUrl = "http://www.gradle.org/doc/${->version}/javadoc"
@@ -91,11 +116,38 @@ tasks.withType(AssembleDslDocTask) {
     classDocbookDir = dslSrcDir
 }
 
+task configureCss << {
+    def images = fileTree(dir: "src/docs/css/images", include: "*.*").files.collectEntries {
+        [it.name, it.bytes.encodeBase64().toString()]
+    }
+
+    def fonts = configurations.fonts.resolvedConfiguration.resolvedArtifacts.collectEntries {
+        def id = it.moduleVersion.id
+        ["${id.group}-${id.name}".toString(), it.file.bytes.encodeBase64().toString()]
+    }
+
+    ext.tokens = images + fonts
+    css.inputs.property 'tokens', tokens
+    css.filter org.apache.tools.ant.filters.ReplaceTokens, tokens: tokens
+}
+
+task css(type: Sync, dependsOn: configureCss) {
+    into "$buildDir/css"
+    from "src/docs/css"
+    include "*.css"
+}
+
+ext.cssFiles = fileTree(css.destinationDir) {
+    builtBy css
+}
+
 task samples(type: ExtractSnippetsTask) {
     source samplesSrcDir
     exclude 'userguideOutput/**'
     exclude 'userguide/tutorial/antLoadfileResources/**'
     exclude '**/readme.xml'
+    exclude '**/build/**'
+    exclude '**/.gradle/**'
     destDir = samplesDir
     snippetsDir = new File(buildDir, 'snippets')
     doLast {
@@ -113,10 +165,8 @@ task userguideStyleSheets(type: Copy) {
     from(stylesheetsDir) {
         include '*.xsl'
     }
-    from(cssSrcDir) {
-        include '*.css'
-    }
-    from(zipTree(configurations.userGuideStyleSheets.singleFile)) {
+    from(cssFiles)
+    from({ zipTree(configurations.userGuideStyleSheets.singleFile) }) {
         // Remove the prefix
         eachFile { fcd -> fcd.path = fcd.path.replaceFirst('^docbook-xsl-[0-9\\.]+/', '') }
     }
@@ -134,7 +184,7 @@ task samplesDocs(type: Docbook2Xhtml) {
     stylesheetName = 'standaloneHtml.xsl'
 }
 
-task dslMetaData(type: ExtractDslMetaDataTask) {  //TODO SF: parseSourceCode
+task dslMetaData(type: ExtractDslMetaDataTask) {
     source { groovydoc.source }
     destFile = new File(docbookSrc, 'dsl-meta-data.bin')
 }
@@ -158,9 +208,7 @@ task dslHtml(type: Docbook2Xhtml) {
     source dslStandaloneDocbook
     destDir = new File(docsDir, 'dsl')
     stylesheetName = 'dslHtml.xsl'
-    resources = fileTree(cssSrcDir) {
-        include '*.css'
-    } + fileTree(dslSrcDir) {
+    resources = cssFiles + fileTree(dslSrcDir) {
         include '*.js'
     }
 }
@@ -189,10 +237,7 @@ task userguideHtml(type: Docbook2Xhtml) {
     stylesheetName = 'userGuideHtml.xsl'
     resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
-    }
-    resources += fileTree(cssSrcDir) {
-        include '*.css'
-    }
+    } + cssFiles
 }
 
 task userguideSingleHtml(type: Docbook2Xhtml) {
@@ -201,10 +246,7 @@ task userguideSingleHtml(type: Docbook2Xhtml) {
     stylesheetName = 'userGuideSingleHtml.xsl'
     resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
-    }
-    resources += fileTree(cssSrcDir) {
-        include '*.css'
-    }
+    } + cssFiles
 }
 
 task pdfUserguideXhtml(type: Docbook2Xhtml) {
@@ -213,14 +255,11 @@ task pdfUserguideXhtml(type: Docbook2Xhtml) {
     stylesheetName = 'userGuidePdf.xsl'
     resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
-    }
-    resources += fileTree(cssSrcDir) {
-        include '*.css'
-    }
+    } + cssFiles
 }
 
 task userguidePdf(type: Xhtml2Pdf, dependsOn: pdfUserguideXhtml) {
-    inputs.dir cssSrcDir
+    inputs.files cssFiles
     sourceFile = pdfUserguideXhtml.destFile
     destFile = new File(userguideDir, 'userguide.pdf')
     classpath = configurations.userGuideTask
@@ -238,9 +277,9 @@ task javadoc(type: Javadoc) {
     options.docEncoding = 'utf-8'
     options.charSet = 'utf-8'
     options.addStringOption "stylesheetfile", stylesheetFile.absolutePath
-    source groovyProjects().collect {project -> project.sourceSets.main.allJava }
+    source publicGroovyProjects.collect {project -> project.sourceSets.main.allJava }
     destinationDir = new File(docsDir, 'javadoc')
-    classpath = files(groovyProjects().collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.output] })
+    classpath = files(publicGroovyProjects.collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.output] })
     include 'org/gradle/api/**'
     include 'org/gradle/*'
     include 'org/gradle/external/javadoc/**'
@@ -271,7 +310,7 @@ task configureGroovydoc {
 
 task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
     group = 'documentation'
-    source groovyProjects().collect {project -> project.sourceSets.main.groovy + project.sourceSets.main.java }
+    source publicGroovyProjects.collect {project -> project.sourceSets.main.groovy + project.sourceSets.main.java }
     destinationDir = new File(docsDir, 'groovydoc')
     classpath = javadoc.classpath
     includes = javadoc.includes
@@ -285,19 +324,6 @@ task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
         def index = new File(destinationDir, "index.html")
         index.text = index.text.replace("{todo.title}", windowTitle) // workaround groovydoc bug
     }
-
-    gradle.taskGraph.whenReady {
-        if (it.hasTask(groovydoc)) {
-            def systemCharset = java.nio.charset.Charset.defaultCharset().name()
-            if (systemCharset != "UTF-8") {
-                if (!isSnapshot) {
-                    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 checkstyleApi(type: Checkstyle) {
@@ -325,21 +351,14 @@ task userguide {
     group = 'documentation'
 }
 
-task docs {
-    dependsOn javadoc, groovydoc, userguide, distDocs, samplesDocs, dslHtml
-    description = 'Generates all documentation'
-    group = 'documentation'
-}
-
-task docsZip(type: Zip) {
-    from project.outputs.docs
-}
-
 import org.gradle.plugins.pegdown.PegDown
 import org.gradle.plugins.jsoup.Jsoup
 
-task editReleaseNotes() << {
-    Class.forName("java.awt.Desktop").newInstance().edit(file("src/docs/release/notes.md"))
+task editReleaseNotes() {
+    group = "release notes"
+    doLast {
+        Class.forName("java.awt.Desktop").newInstance().edit(file("src/docs/release/notes.md"))
+    }
 }
 
 task releaseNotesMarkdown(type: PegDown) {
@@ -350,13 +369,15 @@ task releaseNotesMarkdown(type: PegDown) {
 task decorateReleaseNotes(type: Jsoup) {
     source releaseNotesMarkdown
     destination "$buildDir/release-notes/notes-decorated.html"
-    
+
+    inputs.files cssFiles
     inputs.file "release-notes-transform.gradle"
     apply from: "release-notes-transform.gradle"
 }
 
 import org.apache.tools.ant.filters.*
 task releaseNotes(type: Copy) {
+    group = "release notes"
     ext.fileName = "release-notes.html"
     into "$docsDir"
     from decorateReleaseNotes, {
@@ -367,8 +388,21 @@ task releaseNotes(type: Copy) {
     }
 }
 
-task viewReleaseNotes(dependsOn: releaseNotes) << {
-    Class.forName("java.awt.Desktop").newInstance().browse(new File(releaseNotes.destinationDir, releaseNotes.fileName).toURI())
+task viewReleaseNotes(dependsOn: releaseNotes) {
+    group = "release notes"
+    doLast {
+        Class.forName("java.awt.Desktop").newInstance().browse(new File(releaseNotes.destinationDir, releaseNotes.fileName).toURI())
+    }
+}
+
+task docs {
+    dependsOn javadoc, groovydoc, userguide, distDocs, samplesDocs, dslHtml, releaseNotes
+    description = 'Generates all documentation'
+    group = 'documentation'
+}
+
+task docsZip(type: Zip) {
+    from project.outputs.docs
 }
 
 class Xhtml2Pdf extends DefaultTask {
diff --git a/subprojects/docs/release-notes-transform.gradle b/subprojects/docs/release-notes-transform.gradle
index 96cb33f..80337d3 100644
--- a/subprojects/docs/release-notes-transform.gradle
+++ b/subprojects/docs/release-notes-transform.gradle
@@ -16,15 +16,12 @@ import com.uwyn.jhighlight.renderer.XhtmlRendererFactory
 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")
+        baseStyleFile = file("$cssFiles.dir/base.css")
+        releaseNotesStyleFile = file("$cssFiles.dir/release-notes.css")
         scriptFile = file("src/docs/release/content/script.js")
     }
     
-    inputs.files([styleFile, fontRegularFile, fontBoldFile, logoFile, jqueryFile, scriptFile])
+    inputs.files([baseStyleFile, releaseNotesStyleFile, configurations.jquery, scriptFile])
     
     transform {
         outputSettings().indentAmount(2).prettyPrint(true)
@@ -34,17 +31,26 @@ decorateReleaseNotes {
         head().
             append("<meta charset='utf-8'>").
             append("<title>Gradle @version@ Release Notes</title>")
+
+        head().append("<style>p{}</style>").children().last().childNode(0).attr("data", baseStyleFile.text + releaseNotesStyleFile.text)
         
-        def styleText = styleFile.text.
-            replace("@regular-font-base64@", fontRegularFile.bytes.encodeBase64().toString()).
-            replace("@bold-font-base64@", fontBoldFile.bytes.encodeBase64().toString())
-        
-        head().append("<style>p{}</style>").children().last().childNode(0).attr("data", 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", configurations.jquery.singleFile.text)
         head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
     }
-    
+
+    // Add the extra doc links
+    transform {
+        body().append("""
+            <h2>Gradle @version@ documentation links</h2>
+            <ul>
+                <li><a href='dsl/index.html'>DSL Reference</a></li>
+                <li><a href='userguide/userguide.html'>User Guide</a></li>
+                <li><a href='javadoc/index.html'>Javadoc</a></li>
+                <li><a href='groovydoc/index.html'>Groovdoc</a></li>
+            </ul>
+        """)
+    }
+
     // wrap each h2 section in section.topic
     transform {
         def heading = body().select("h2").first()
@@ -74,7 +80,7 @@ decorateReleaseNotes {
     // 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")) {
+        for (heading in body().select(".topic").select("h3")) {
             def detail = []
             
             Element next = heading.nextElementSibling()
@@ -157,14 +163,14 @@ decorateReleaseNotes {
     
     // 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()}' />")
+        body().prepend("<h3 class='releaseinfo'>Version @version@</h3>")
+        body().prepend("<h1>Gradle Release Notes</h1>")
     }
     
     // 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> —")
+        footer.html("Gradle @version@ Release Notes<br />")
     }
     
     // Syntax highlighting
@@ -198,6 +204,19 @@ decorateReleaseNotes {
             }
         }
     }
-    
-    
+
+    // Wrap the page in a text container to get the margins
+    transform {
+        def bodyContent = body().children().remove()
+        body().prepend("<div class='text-container'/>")
+        body().children()[0].html(bodyContent.outerHtml())
+    }
+
+    // Turn Gradle issue numbers into issue links
+    transform {
+        def rewritten = body().html().replaceAll(~/GRADLE-\d+/) {
+            "<a href='http://issues.gradle.org/browse/${it}'>${it}</a>"
+        }
+        body().html(rewritten)
+    }
 }
\ 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 6fce32a..a673e35 100644
--- a/subprojects/docs/src/docs/css/base.css
+++ b/subprojects/docs/src/docs/css/base.css
@@ -1,164 +1,208 @@
-
-/*
- * Basic layout
- */
-
-html {
-    margin: 0;
-    /*height: 100%;*/
-    /*min-height: 100%*/
+/* 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%;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal; line-height: 1;color: #007042;}
+h1 {font-size:4em;}
+h2 {font-size:2.2em;}
+h3 {font-size:1.5em;}
+h4 {font-size:1.3em;}
+h5 {font-size:1.1em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+.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 {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:normal;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+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;}
+
+::selection {
+    background-color: #DDF0DD;
+}
+
+ 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 lato-regular@') format('truetype');
+}
+
+ at font-face {
+  font-family: 'Lato';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Lato Italic'), local('Lato-Italic'), url('data:font/opentype;base64, at lato-regular-italic@') format('truetype');
+}
+
+ 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 lato-bold@') format('truetype');
+}
+
+ at font-face {
+  font-family: 'Lato';
+  font-style: italic;
+  font-weight: 700;
+  src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('data:font/opentype;base64, at lato-bold-italic@') format('truetype');
+}
+
+ at font-face {
+  font-family: 'Ubuntu Mono';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url('data:font/opentype;base64, at ubuntumono-bold@') format('truetype');
+}
+ at font-face {
+  font-family: 'Ubuntu Mono';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url('data:font/opentype;base64, at ubuntumono-regular@') format('truetype');
+}
+ at font-face {
+  font-family: 'Ubuntu Mono';
+  font-style: italic;
+  font-weight: 700;
+  src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url('data:font/opentype;base64, at ubuntumono-bold-italic@') format('truetype');
+}
+ at font-face {
+  font-family: 'Ubuntu Mono';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url('data:font/opentype;base64, at ubuntumono-regular-italic@') format('truetype');
 }
 
 body {
-    margin: 0;
-    padding: 0;
-    background-color: white;
-    line-height: 150%;
-}
-
-body, td, div {
-    font-family: 'DejaVu Sans', 'Lucida Grande', 'Verdana', sans-serif;
-    font-size: 11pt;
-    color: #444444;
-}
-
-div {
-    margin: 0;
-    padding: 0;
-}
-
-a {
-    color: #444444;
-}
-
-a:visited {
-    color: #444444;
-}
-
-h1, h2, h3, h4, h5 {
-    color: #444444;
-    line-height: 150%;
-    font-weight: bold;
-    font-family: 'DejaVu Sans', 'Lucida Grande', helvetica, sans-serif;
+  font-size: 14px;
+  font-family: 'Lato', Arial, serif;
+  margin: 28px 28px 14px;
+  color: #333;
+  background: url('data:image/gif;base64, at gradle-logo_25o.gif@') no-repeat right top;
 }
 
-.heading {
-    font-family: 'DejaVu Sans', 'Lucida Grande', helvetica, sans-serif;
+p, ol, ul, .para {
+  margin: 0 0 16px;
 }
 
-code, pre {
-    font-family: 'Lucida Console', 'DejaVu Sans Mono', Courier, monospace;
+h1, h2, h3, h4, h5, h6 {
+  margin-bottom: 14px
 }
 
-/*
- * Docbook content
- */
-
-/*
- * Links
- */
-a.link {
-    white-space: nowrap;
+h2 {
+  margin-top: 32px;
 }
 
-/*
- * Lists
- */
-
-.variablelist dt {
-    font-weight: bold;
+h3, h4, h5, h6 {
+  margin-top: 22px;
 }
 
-.variablelist dt a {
-    font-weight: normal;
+.text-container,
+div.book,
+div.chapter {
+  margin-left: 10px;
+  margin-right: 10px;
 }
 
-/*
- * Examples, Figures
- */
-
-pre {
-    background-color: #f5f5f5;
-    padding-top: 1em;
-    padding-bottom: 1em;
-    padding-left: 1.2em;
-    padding-right: 1.2em;
-    margin-top: 0.6em;
-    margin-bottom: 0.6em;
-    line-height: 120%;
+.text-container h1, .text-container h2, .text-container h3, .text-container h4, .text-container h5,
+div.book h1, div.book h2, div.book h3, div.book h4, div.book h5,
+div.chapter h1, div.chapter h2, div.chapter h3, div.chapter h4, div.chapter h5 {
+  margin-left: -10px;
 }
 
-.programlisting {
-    border-left: solid #d0d0d0 0.5em;
-    /*overflow-x: auto;*/
+code, tt, pre {
+  font-family: "Ubuntu Mono", courier, monospace;
 }
 
-.screen {
-    border-left: solid #d0d0d0 0.5em;
-    /*overflow-x: auto;*/
+h1, h2, h3, h4 {
+  text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.4);
 }
 
-.example, .figure, .table {
-    margin-top: 0;
-    margin-bottom: 1.8em;
+h5, h6 {
+ color: #666;
 }
 
-.example .title, .figure .title, .table .title {
-    white-space: nowrap;
-    /*overflow-x: hidden;*/
-    margin-top: 0;
-    margin-bottom: 0.6em;
-}
-
-.example pre, .figure pre {
-    margin: 0;
-    margin-bottom: 0.6em;
+h1 {
+    font-weight: bold;
 }
 
-.example-contents > p {
-    display: block;
-    margin: 0;
-    margin-bottom: 0.2em;
+h3.releaseinfo {
+   color: #666666;
+   padding-top: 0;
 }
 
-.example-break, .figure-break {
-    display: none
+a {
+    color: #007042;
+    text-decoration: none;
 }
-
-.exampleLocation {
-    margin-top: 0.5em;
-    padding-left: 1.7em;
-    background: #FEFEDD;
-    color: #777744;
+a:visited {
+    color: #007042;
 }
-
-.exampleLocation p {
-    padding-top: 0.4em;
-    padding-bottom: 0.4em;
+a:hover {
+    color: #007042;
+    border-bottom: 1px dotted #007042;
 }
-
-.exampleLocation .emphasis em {
-    font-style: normal;
-    font-weight: bold;
+a:focus {
+    outline: thin dotted;
 }
-
-.cmdsynopsis {
-    font-family: Courier, monospace;
-    margin-left: 2em;
+a:hover, a:active {
+    outline: 0;
 }
 
-/*
- * Tables
- */
-
-.table table {
+table {
     border-collapse: collapse;
     font-size: 100%;
     min-width: 50%;
     border: solid #d0d0d0 1px;
 }
 
-.table table td {
+table td {
     text-align: left;
     vertical-align: text-top;
     padding-left: 0.8em;
@@ -167,79 +211,46 @@ pre {
     padding-bottom: 0.3em;
 }
 
-.table table thead td {
+table thead td, table th {
     font-weight: bold;
     border-bottom: solid #d0d0d0 1px;
     background-color: #f2f2f2;
 }
 
-/*
- * Footnotes, notes, tips
- */
-
-.footnote sup {
-    vertical-align: baseline;
-    font-size: 100%;
-}
-
-.note, .tip {
-    padding-top: 1em;
-    padding-bottom: 0;
-    padding-left: 1.2em;
-    padding-right: 1.2em;
-    margin-bottom: 1em;
+table th.border-right {
+    border-right: solid #d0d0d0 1px;
 }
 
-.note {
-    background: #ebf4f7;
-    border: solid #c3d9e6 1px;
+table th.no-border-bottom {
+    border-bottom: none;
 }
 
-.note .title {
-    color: #5283a1;
+pre {
+  border-radius: 5px;
+  padding: 0.8em;
+  line-height: 1.3;
 }
 
-.tip {
-    background: #FAF7F5;
-    border: solid #e2d2c9 1px;
-    float: right;
-    clear: right;
-    margin-left: 2em;
-    margin-top: 1em;
-    margin-bottom: 1em;
-    max-width: 30%;
-    width: 30%;
+pre.code, pre.programlisting {
+  background-color: #3F3F3F;
+  color: white;
 }
 
-.tip .title {
-    color: #80614D;
+pre.screen, pre.tt {
+  color: darkGreen;
+  background-color: white;
+  box-shadow: inset 0 0 7px 0px darkGray;
 }
 
-/* Remove top margins on headings inside notes and tips */
-.tip h1, .note h1
-.tip h2, .note h2,
-.tip h3, .note h3,
-.tip h4, .note h4,
-.tip h5, .note h5,
-.tip h6, .note h6
-{
-   margin-top: 0;
+tbody tr:nth-child(even) td, tbody tr.even td {
+    background: #F7F7F7;
 }
 
-/**
- * Inline content
- */
-.literal, .userinput, .filename {
-    white-space: nowrap;
+li em, p em {
+  padding: 0 1px 0 1px;
+  text-decoration: underline;
 }
 
-/*
- * Code highlighting
- */
-.hl-string, .hl-number, .hl-value { color: #51913f; }
-
-.hl-keyword { font-weight: bold; }
-
-.hl-comment, .hl-doccomment { color: #929292; }
-
-.hl-annotation { color: #777744; }
+code em, tt em {
+  text-decoration: none;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/docs.css b/subprojects/docs/src/docs/css/docs.css
new file mode 100644
index 0000000..a4e0bac
--- /dev/null
+++ b/subprojects/docs/src/docs/css/docs.css
@@ -0,0 +1,134 @@
+.variablelist dt {
+    font-weight: bold;
+}
+
+.variablelist dt a {
+    font-weight: normal;
+}
+
+.itemizedlist li p {
+    margin: 0;
+}
+
+.example, .figure, .table {
+    margin-top: 0;
+    margin-bottom: 1.8em;
+}
+
+.example .title, .figure .title, .table .title {
+    white-space: nowrap;
+    /*overflow-x: hidden;*/
+    margin-top: 0;
+    margin-bottom: 0.6em;
+}
+
+.example pre, .figure pre {
+    margin: 0;
+    margin-bottom: 0.6em;
+}
+
+.example-contents > p {
+    display: block;
+    margin: 0;
+    margin-bottom: 0.2em;
+}
+
+.example-break, .figure-break {
+    display: none
+}
+
+.exampleLocation {
+    margin-top: 0.5em;
+    padding-left: 1.7em;
+    background: #FEFEDD;
+    color: #777744;
+    border: 1px solid #DBDBA8;
+    padding-top: 0.4em;
+    padding-bottom: 0.4em;
+}
+
+.exampleLocation .emphasis em {
+    font-style: normal;
+    font-weight: bold;
+}
+
+.cmdsynopsis {
+    font-family: "Ubuntu Mono", courier, monospace;
+    margin-left: 2em;
+}
+
+.appendix h1 {
+    margin-bottom: 1.0em;
+}
+
+.footnote sup {
+    vertical-align: baseline;
+    font-size: 100%;
+}
+
+.note > :last-child, .tip > :last-child, .examplelocation > :last-child {
+    margin-bottom: 0px
+}
+
+.note, .tip {
+    padding: 1em 1.2em;
+    margin-bottom: 1em;
+}
+
+.note {
+    background: #ebf4f7;
+    border: solid #c3d9e6 1px;
+}
+
+.note .title {
+    color: #5283a1;
+    margin-left: 0px;
+}
+
+.tip {
+    background: #FAF7F5;
+    border: solid #e2d2c9 1px;
+    float: right;
+    clear: right;
+    margin-left: 2em;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    max-width: 30%;
+    width: 30%;
+}
+
+.tip .title {
+    color: #80614D;
+    margin-left: 0px;
+}
+
+.tip h1, .note h1
+.tip h2, .note h2,
+.tip h3, .note h3,
+.tip h4, .note h4,
+.tip h5, .note h5,
+.tip h6, .note h6
+{
+   margin-top: 0;
+}
+
+.literal, .userinput, .filename {
+    white-space: nowrap;
+}
+
+/*
+ * Code highlighting
+ */
+.hl-string, .hl-number, .hl-value { color: #83C283; }
+
+.hl-keyword { color: #DDC498 }
+
+.hl-comment, .hl-doccomment { color: #AFAFAF; }
+
+.hl-annotation { color: #DDDD97; }
+
+.hl-directive { color: white !important; }
+
+a.link {
+    white-space: nowrap;
+}
diff --git a/subprojects/docs/src/docs/css/dsl.css b/subprojects/docs/src/docs/css/dsl.css
index 6ddf2e6..36c47f8 100644
--- a/subprojects/docs/src/docs/css/dsl.css
+++ b/subprojects/docs/src/docs/css/dsl.css
@@ -1,16 +1,37 @@
 
 .caution {
-    font-weight: bold;
+  font-size: 13px;
+  color: #AF5252;
+  background-color: pink;
+  text-transform: lowercase;
+  font-style: italic;
+  border-radius: 3px;
+  display: inline-block;
+  padding: 0px 5px;
+  margin-bottom: 8px;
+}
+
+.caution :last-child {
+  margin-bottom: 0;
+}
+
+table .caution {
+  float: left;
+  font-size: 12px;
+  margin-right: 6px;
+  margin-top: 2px;
+  margin-bottom: 0px;
+  text-transform: lowercase;
 }
 
 .sidebar {
     margin-top: 2em;
     margin-left: 1.8em;
-    margin-right: 1.4em;
+    margin-right: 0em;
     position: absolute;
     top: 0;
     left: 0;
-    width: 13em;
+    width: 15em;
 }
 
 .content {
@@ -37,6 +58,7 @@
     margin-bottom: 0.4em;
     font-weight: bold;
     color: #505050;
+    font-size: 15px
 }
 
 .sidebar li.selected {
@@ -59,10 +81,6 @@
     padding-left: 1em;
 }
 
-.book .titlepage .releaseinfo {
-    color: #666666;
-}
-
 .segmentedlist {
     margin-top: 1.6em;
     margin-bottom: 1.6em;
@@ -71,8 +89,8 @@
 .segmentedlist table, .table table {
     border: solid #d0d0d0 1px;
     border-collapse: collapse;
-    margin-left: -0.8em;
-    margin-right: -0.8em;
+    margin-left: -10px;
+    margin-right: -10px;
 }
 
 .segmentedlist th {
@@ -109,12 +127,19 @@
     -webkit-border-radius: 4px;
     -moz-border-radius: 4px;
     border-radius: 4px;
+    font-family: "Ubuntu Mono", courier, monospace;
+    text-shadow: none;
 }
 
 .signature .literal {
-    font-size: 120%;
-    font-weight: bold;
-    color: #5283a1;
-    padding-left: 0.4em;
-    padding-right: 0.2em;
+    color: #04598D;
+    padding-left: 0.1em;
 }
+
+.content tbody p {
+    margin: 0;
+}
+
+div.chapter h5 {
+    margin-left: 0px;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/images/gradle-logo_25o.gif b/subprojects/docs/src/docs/css/images/gradle-logo_25o.gif
new file mode 100644
index 0000000..13a4032
Binary files /dev/null and b/subprojects/docs/src/docs/css/images/gradle-logo_25o.gif differ
diff --git a/subprojects/docs/src/docs/css/release-notes.css b/subprojects/docs/src/docs/css/release-notes.css
new file mode 100644
index 0000000..c0445f8
--- /dev/null
+++ b/subprojects/docs/src/docs/css/release-notes.css
@@ -0,0 +1,72 @@
+img.logo {
+  margin-bottom: 1em;
+}
+
+section.major-detail {
+  margin: 0 10px 18px;
+  padding: 18px;
+  border-left: 2px solid #999;
+  border: 1px dotted #999;
+  background-color: #FCFCFC;
+  border-radius: 5px;
+}
+
+section.major-detail > :first-child {
+  margin-top: 0px;
+}
+
+section.major-detail > :last-child {
+  margin-bottom: 0px;
+}
+
+section.minor-detail > :last-child {
+  margin-bottom: 0px;
+}
+
+h4 {
+  padding-left: 10px;
+}
+
+section.minor-detail {
+  padding: 0 10px;
+}
+
+ul.toc {
+  font-size: 15px
+}
+
+ul.toc-sub {
+  margin-bottom: 0px;
+}
+
+button.display-toggle {
+  cursor: pointer;
+}
+
+section.footer {
+  font-size: 75%;
+  margin-top: 3em;
+  padding-top: 1em;
+  width: 100%;
+  border-top: solid #d0d0d0 0.4em;
+  text-align: center;
+}
+
+.java_keyword {
+  color: #DDC498;
+}
+.java_comment {
+  color: #AFAFAF;
+}
+.java_plain, .java_type {
+
+}
+.java_separator {
+
+}
+.java_operator {
+  color: #C7B390;
+}
+.java_literal {
+  color: #83C283;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/style.css b/subprojects/docs/src/docs/css/style.css
deleted file mode 100644
index c3934f8..0000000
--- a/subprojects/docs/src/docs/css/style.css
+++ /dev/null
@@ -1,58 +0,0 @@
-
-body {
-    margin-top: 2em;
-    margin-bottom: 2em;
-    margin-left: 4em;
-    margin-right: 4em;
-}
-
-a {
-    text-decoration: none;
-    border-bottom: dotted 1px;
-    /*outline: none;*/
-}
-
-p {
-   /* ensure paragraphs remain close to their parent heading */
-   margin-top: 0;
-   margin-bottom: 1em;
-}
-
-h1, h2, h3, h4, h5 {
-    /* ensure paragraphs remain close to their parent heading */
-    margin-bottom: 0.2em;
-    color: #4a9935;
-}
-
-h1 {
-    font-size: 180%;
-}
-
-h2 {
-    font-size: 180%;
-}
-
-h3 {
-    font-size: 110%;
-}
-
-.chapter h1 {
-    margin-bottom: 1.0em;
-}
-
-.appendix h1 {
-    margin-bottom: 1.0em;
-}
-
-.section h2 {
-    font-size: 130%;
-    margin-top: 1.4em;
-}
-
-h1 a, h2 a, h3 a, h4 a, h5 a {
-    color: inherit;
-}
-
-h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited {
-    color: inherit;
-}
diff --git a/subprojects/docs/src/docs/css/userguide.css b/subprojects/docs/src/docs/css/userguide.css
index 263547f..9a4d2ac 100644
--- a/subprojects/docs/src/docs/css/userguide.css
+++ b/subprojects/docs/src/docs/css/userguide.css
@@ -12,7 +12,21 @@
 }
 
 .book .toc, .book .list-of-examples {
-    margin-top: 2em;
+    margin-top: 1.5em;
+}
+
+div.toc {
+    font-size: 15px;
+}
+
+div.toc p { /* Is "Table Of Contents" header */
+    font-size: 28px;
+    color: #007042;
+    margin-bottom: 0.5em;
+}
+
+div.toc p b {
+    font-weight: normal;
 }
 
 /*
@@ -20,10 +34,7 @@
  */
 
 .navheader {
-    margin-bottom: 3em;
-    padding-bottom: 1em;
-    width: 100%;
-    border-bottom: solid #d0d0d0 0.4em;
+    display: none;
 }
 
 .navfooter {
@@ -37,7 +48,7 @@
     color: #a2a2a2;
     font-size: 90%;
     width: 100%;
-    text-align: right;
+    text-align: center;
 }
 
 .navbar a {
@@ -53,54 +64,20 @@
  * TITLEPAGE
  */
 
-.book > .titlepage {
-    margin-top: 2.2em;
-    margin-bottom: 2em;
-}
-
-.book .titlepage div.title {
-    border: solid #d0d0d0 1px;
-    background-color: #f5f5f5;
-    margin-left: -1.8em;
-    margin-right: -1.8em;
-    padding-left: 1.8em;
-    padding-right: 1.8em;
-    margin-bottom: 2.2em;
-}
-
-.titlepage h1.title {
-    font-size: 280%;
-    margin-bottom: 0;
-    color: #4a9935;
-}
-
-.titlepage h2.subtitle {
-    font-size: 160%;
-    font-style: italic;
-    margin-top: 0;
-    margin-bottom: 0;
-    color: #4a9935;
-}
-
-.titlepage .releaseinfo {
-    color: #666666;
-    font-size: 120%;
-    margin-top: 2em;
-    margin-bottom: 2.6em;
-}
-
 .titlepage .author {
     color: #666666;
 }
 
 .titlepage .copyright {
     color: #a2a2a2;
-    font-size: 90%;
 }
 
 .titlepage .legalnotice {
     color: #a2a2a2;
-    font-size: 90%;
+}
+
+.titlepage p {
+    margin: 0;
 }
 
 /*
@@ -109,3 +86,7 @@
 .book .chapter {
     margin-top: 4em;
 }
+
+div.example code.filename {
+  font-weight: bold;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index 881b3f4..0c51bcc 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -73,8 +73,9 @@
     <!--
       -
       - 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.
+      - There are 2 ways to include a new types to this guide:
+      -     * Types referenced by a property are automatically included, if there is a corresponding ${typename}.xml in the DSL source directory.
+      -     * Types listed in one of the following tables are included. There must be a corresponding ${typename}.xml in the DSL source directory.
       -
       - 2. Adding new sections:
       - The section title should end with 'types' (see AssembleDslDocTask.mergeContent)
@@ -112,6 +113,9 @@
             <tr>
                 <td>subprojects</td>
             </tr>
+            <tr>
+                <td>publishing</td>
+            </tr>
         </table>
         <para>A build script is also a Groovy script, and so can contain those elements allowed in a Groovy script,
             such as method definitions and class definitions.
@@ -157,8 +161,17 @@
                 <td>org.gradle.api.plugins.ExtensionAware</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.PublishingExtension</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.plugins.ExtraPropertiesExtension</td>
             </tr>
+            <tr>
+                <td>org.gradle.api.publish.ivy.IvyPublication</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.publish.ivy.IvyModuleDescriptor</td>
+            </tr>
         </table>
     </section>
 
@@ -176,8 +189,30 @@
             <tr>
                 <td>org.gradle.api.artifacts.dsl.ArtifactHandler</td>
             </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Help Task types</title>
+        <para>Below are the task types that are available for every Gradle project.
+            Those task types can also be declared and configured directly in the build script.
+        </para>
+        <table>
+            <title>Help Task types</title>
+            <tr>
+                <td>org.gradle.api.tasks.diagnostics.TaskReportTask</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.diagnostics.ProjectReportTask</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.diagnostics.DependencyReportTask</td>
+            </tr>
             <tr>
-                <td>org.gradle.api.tasks.testing.logging.TestLoggingContainer</td>
+                <td>org.gradle.api.tasks.diagnostics.DependencyInsightReportTask</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.diagnostics.PropertyReportTask</td>
             </tr>
         </table>
     </section>
@@ -212,9 +247,6 @@
                 <td>org.gradle.api.tasks.Directory</td>
             </tr>
             <tr>
-                <td>org.gradle.api.tasks.diagnostics.DependencyReportTask</td>
-            </tr>
-            <tr>
                 <td>org.gradle.plugins.ear.Ear</td>
             </tr>
             <tr>
@@ -236,6 +268,9 @@
                 <td>org.gradle.api.tasks.bundling.Jar</td>
             </tr>
             <tr>
+                <td>org.gradle.api.tasks.compile.JavaCompile</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.javadoc.Javadoc</td>
             </tr>
             <tr>
@@ -257,12 +292,6 @@
                 <td>org.gradle.api.plugins.quality.Pmd</td>
             </tr>
             <tr>
-                <td>org.gradle.api.tasks.diagnostics.PropertyReportTask</td>
-            </tr>
-            <tr>
-                <td>org.gradle.api.tasks.diagnostics.ProjectReportTask</td>
-            </tr>
-            <tr>
                 <td>org.gradle.api.tasks.scala.ScalaCompile</td>
             </tr>
             <tr>
@@ -281,9 +310,6 @@
                 <td>org.gradle.api.tasks.bundling.Tar</td>
             </tr>
             <tr>
-                <td>org.gradle.api.tasks.diagnostics.TaskReportTask</td>
-            </tr>
-            <tr>
                 <td>org.gradle.api.tasks.testing.Test</td>
             </tr>
             <tr>
@@ -298,6 +324,9 @@
             <tr>
                 <td>org.gradle.api.tasks.bundling.Zip</td>
             </tr>
+            <tr>
+                <td>org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds</td>
+            </tr>
         </table>
     </section>
 
@@ -380,34 +409,4 @@
         </table>
     </section>
 
-    <section id="dsl-element-types">
-        <title>Backwards compatibility across Gradle versions</title>
-        <para>Most DSL elements documented here are considered <firstterm>public</firstterm> elements. This means that they are fully supported
-            and will remain unchanged until at least the next major version of Gradle. And in most cases, a feature will be supported well
-            beyond the next major version.
-            By <firstterm>major version</firstterm>, we mean a change to the first part of the version number. So, if a public feature is available
-            in Gradle 1.2, it will continue to be available in Gradle 1.3, Gradle 1.4, and so on until at least Gradle 2.0.
-        </para>
-        <para>Some elements of the DSL are marked as <firstterm>deprecated</firstterm>. This means that the element is not longer
-            intended to be used, and will be removed at some point in the future. In most cases, there is a replacement for the deprecated element,
-            and this will be described in the documentation.
-        </para>
-        <para>
-            A deprecated element will not be removed before the next major version of Gradle. You can continue to use the element
-            until then. However, you will receive a deprecation warning when you run a build that uses the element, to remind you that the
-            element is to be removed.
-            We recommend that you change your code so that it no longer uses the deprecated element and uses the appropriate replacement instead.
-        </para>
-        <para>Some elements of the DSL are marked as <firstterm>experimental</firstterm>. This means that the element is a work-in-progress,
-            and may require some changes before it is ready to become public. These changes are based on feedback as these features are used in
-            real builds. Please feel free to try an experimental element and give us your feedback, but please be aware that it may change in
-            future versions of Gradle. Note that this includes minor versions.
-        </para>
-        <para>Finally, there are also some <firstterm>internal</firstterm> undocumented features present in Gradle.
-            These are intended to be used in Gradle itself, and may change at any time.
-            Internal features are not documented in this DSL reference, the user guide, or in the javadoc and groovydoc API documentation.
-            If a feature is not documented, then it is likely an internal feature, and we recommend that you avoid using it.
-        </para>
-    </section>
-
 </book>
\ No newline at end of file
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 40950c3..9d7061a 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
@@ -17,6 +17,9 @@
                 <td>state</td>
             </tr>
             <tr>
+                <td>incoming</td>
+            </tr>
+            <tr>
                 <td>visible</td>
             </tr>
             <tr>
@@ -80,6 +83,9 @@
             <tr>
                 <td>copyRecursive</td>
             </tr>
+            <tr>
+                <td>getTaskDependencyFromProjectDependency</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds.xml
new file mode 100644
index 0000000..445c79b
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds.xml
@@ -0,0 +1,45 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>compare-gradle-builds</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sourceBuild</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>targetBuild</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>reportDir</td>
+                <td><literal>${project.reporting.file(this.name)}</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>
+            <tr>
+                <td>sourceBuild</td>
+            </tr>
+            <tr>
+                <td>targetBuild</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.GradleBuildInvocationSpec.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.GradleBuildInvocationSpec.xml
new file mode 100644
index 0000000..466dd3f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.buildcomparison.gradle.GradleBuildInvocationSpec.xml
@@ -0,0 +1,39 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>compare-gradle-builds</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>projectDir</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>gradleVersion</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>tasks</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>arguments</td>
+                <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
index 8690c6b..180ee4d 100644
--- 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
@@ -33,6 +33,30 @@
                 <td><literal>project.findbugs.ignoreFailures</literal></td>
             </tr>
             <tr>
+                <td>effort</td>
+                <td><literal>project.findbugs.effort</literal></td>
+            </tr>
+            <tr>
+                <td>reportLevel</td>
+                <td><literal>project.findbugs.reportLevel</literal></td>
+            </tr>
+            <tr>
+                <td>visitors</td>
+                <td><literal>project.findbugs.visitors</literal></td>
+            </tr>
+            <tr>
+                <td>omitVisitors</td>
+                <td><literal>project.findbugs.omitVisitors</literal></td>
+            </tr>
+            <tr>
+                <td>includeFilter</td>
+                <td><literal>project.findbugs.includeFilter</literal></td>
+            </tr>
+            <tr>
+                <td>excludeFilter</td>
+                <td><literal>project.findbugs.excludeFilter</literal></td>
+            </tr>
+            <tr>
                 <td>reports</td>
                 <td></td>
             </tr>
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
index 2d34681..87e9a5d 100644
--- 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
@@ -10,12 +10,36 @@
             </thead>
             <tr>
                 <td>toolVersion</td>
-                <td><literal>2.0.0</literal></td>
+                <td><literal>2.0.1</literal></td>
             </tr>
             <tr>
                 <td>reportsDir</td>
                 <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/findbugs</literal></td>
             </tr>
+            <tr>
+                <td>effort</td>
+                <td><literal>'default'</literal></td>
+            </tr>
+            <tr>
+                <td>reportLevel</td>
+                <td><literal>'medium'</literal></td>
+            </tr>
+            <tr>
+                <td>visitors</td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>omitVisitors</td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>includeFilter</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>excludeFilter</td>
+                <td><literal>null</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
new file mode 100644
index 0000000..f46d04e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.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.publish.PublishingExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublishingExtension.xml
new file mode 100644
index 0000000..2b22065
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublishingExtension.xml
@@ -0,0 +1,34 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>publications</td>
+            </tr>
+            <tr>
+                <td>repositories</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>publications</td>
+            </tr>
+            <tr>
+                <td>repositories</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml
new file mode 100644
index 0000000..ba573a3
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyModuleDescriptor.xml
@@ -0,0 +1,30 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>ivy-publish</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>file</td>
+                <td><literal>$buildDir/publications/«publication name»/ivy.xml</literal></td>
+            </tr>
+        </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.api.publish.ivy.IvyPublication.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyPublication.xml
new file mode 100644
index 0000000..7eedb8e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyPublication.xml
@@ -0,0 +1,28 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>descriptor</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>descriptor</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractOptions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractOptions.xml
new file mode 100644
index 0000000..f46d04e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractOptions.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.tasks.compile.CompileOptions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.CompileOptions.xml
new file mode 100644
index 0000000..cbd9b35
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.CompileOptions.xml
@@ -0,0 +1,108 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>failOnError</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>verbose</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>listFiles</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>deprecation</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>warnings</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>encoding</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>optimize</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>debug</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>debugOptions</td>
+                <td></td>
+            </tr>
+            <tr>
+                <td>fork</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>forkOptions</td>
+                <td></td>
+            </tr>
+            <tr>
+                <td>useDepend</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>dependOptions</td>
+                <td></td>
+            </tr>
+            <tr>
+                <td>compiler</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>includeJavaRuntime</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>bootClasspath</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>extensionDirs</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>compilerArgs</td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>useAnt</td>
+                <td><literal>false</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>debug</td>
+            </tr>
+            <tr>
+                <td>fork</td>
+            </tr>
+            <tr>
+                <td>depend</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml
new file mode 100644
index 0000000..6a82a81
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompileOptions.xml
@@ -0,0 +1,78 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>failOnError</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>verbose</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>listFiles</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>encoding</td>
+                <td><literal>UTF-8</literal></td>
+            </tr>
+            <tr>
+                <td>fork</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>forkOptions</td>
+                <td></td>
+            </tr>
+            <tr>
+                <td>optimizationOptions</td>
+                <td><literal>[:]</literal></td>
+            </tr>
+            <tr>
+                <td>stacktrace</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>useAnt</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>includeJavaRuntime</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>stubDir</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>fileExtensions</td>
+                <td><literal>["java", "groovy"]</literal></td>
+            </tr>
+            <tr>
+                <td>keepStubs</td>
+                <td><literal>false</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>fork</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.JavaCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.JavaCompile.xml
new file mode 100644
index 0000000..99339ae
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.JavaCompile.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>java</literal> plugin </td>
+                </tr>
+            </thead>
+            <tr>
+                <td>options</td>
+                <td/>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal><replaceable>sourceSet</replaceable>.java</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.tasks.diagnostics.DependencyInsightReportTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyInsightReportTask.xml
new file mode 100644
index 0000000..81020e2
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyInsightReportTask.xml
@@ -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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>java</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>configuration</td>
+                <td><literal>compile</literal> configuration</td>
+            </tr>
+            <tr>
+                <td>dependencySpec</td>
+                <td>-</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.scala.ScalaCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompile.xml
index 2cb9132..2f47edc 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompile.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompile.xml
@@ -13,6 +13,10 @@
                 <td><literal>project.configurations.scalaTools</literal></td>
             </tr>
             <tr>
+                <td>options</td>
+                <td/>
+            </tr>
+            <tr>
                 <td>scalaCompileOptions</td>
                 <td/>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompileOptions.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompileOptions.xml
new file mode 100644
index 0000000..7971304
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaCompileOptions.xml
@@ -0,0 +1,95 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>useCompileDaemon</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>daemonServer</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>failOnError</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>deprecation</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>unchecked</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>debugLevel</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>optimize</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>encoding</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>force</td>
+                <td><literal>never</literal></td>
+            </tr>
+            <tr>
+                <td>targetCompatibility</td>
+                <td><literal>1.5</literal></td>
+            </tr>
+            <tr>
+                <td>additionalParameters</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>listFiles</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>loggingLevel</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>loggingPhases</td>
+                <td><literal>null</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.tasks.testing.Test.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
index 5dd5877..451b8f3 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
@@ -34,7 +34,7 @@
             </tr>
             <tr>
                 <td>testReport</td>
-                <td><literal>true</literal></td>
+                <td><literal>true</literal> for JUnit, since Gradle 1.3 <literal>false</literal> for TestNG</td>
             </tr>
             <tr>
                 <td>scanForTestClasses</td>
@@ -177,6 +177,9 @@
             <tr>
                 <td>testLogging</td>
             </tr>
+            <tr>
+                <td>options</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLogging.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLogging.xml
new file mode 100644
index 0000000..d374776
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLogging.xml
@@ -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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>events</td>
+            </tr>
+            <tr>
+                <td>minGranularity</td>
+            </tr>
+            <tr>
+                <td>maxGranularity</td>
+            </tr>
+            <tr>
+                <td>displayGranularity</td>
+            </tr>
+            <tr>
+                <td>showExceptions</td>
+            </tr>
+            <tr>
+                <td>showCauses</td>
+            </tr>
+            <tr>
+                <td>showStackTraces</td>
+            </tr>
+            <tr>
+                <td>showStandardStreams</td>
+            </tr>
+            <tr>
+                <td>exceptionFormat</td>
+            </tr>
+            <tr>
+                <td>stackTraceFilters</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.testing.logging.TestLoggingContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml
index cfa0c9b..060acf7 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml
@@ -13,6 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <section>
     <section>
         <title>Properties</title>
@@ -22,38 +23,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-<!--
-            <tr>
-                <td>events</td>
-            </tr>
-            <tr>
-                <td>minGranularity</td>
-            </tr>
-            <tr>
-                <td>maxGranularity</td>
-            </tr>
-            <tr>
-                <td>displayGranularity</td>
-            </tr>
-            <tr>
-                <td>showExceptions</td>
-            </tr>
-            <tr>
-                <td>showCauses</td>
-            </tr>
-            <tr>
-                <td>showStackTraces</td>
-            </tr>
-            <tr>
-                <td>showStandardStreams</td>
-            </tr>
-            <tr>
-                <td>exceptionFormat</td>
-            </tr>
-            <tr>
-                <td>stackTraceFilters</td>
-            </tr>
--->
             <tr>
                 <td>debug</td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
index 49e0909..e0d77b2 100644
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -7,6 +7,9 @@
     <plugin id="groovy" description="Groovy Plugin">
         <extends targetClass="org.gradle.api.tasks.SourceSet" mixinClass="org.gradle.api.tasks.GroovySourceSet"/>
     </plugin>
+    <plugin id="publishing" description="Publishing Plugin">
+        <extends targetClass="org.gradle.api.Project" id="publishing" extensionClass="org.gradle.api.publish.PublishingExtension"/>
+    </plugin>
     <plugin id="scala" description="Scala Plugin">
         <extends targetClass="org.gradle.api.tasks.SourceSet" mixinClass="org.gradle.api.tasks.ScalaSourceSet"/>
     </plugin>
@@ -43,7 +46,7 @@
         <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"/>
+        <extends targetClass="org.gradle.api.Project" id="signing" 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"/>
diff --git a/subprojects/docs/src/docs/release/content/Lato-bold.woff b/subprojects/docs/src/docs/release/content/Lato-bold.woff
deleted file mode 100644
index 68729b7..0000000
Binary files a/subprojects/docs/src/docs/release/content/Lato-bold.woff and /dev/null differ
diff --git a/subprojects/docs/src/docs/release/content/Lato-regular.woff b/subprojects/docs/src/docs/release/content/Lato-regular.woff
deleted file mode 100644
index 26a056b..0000000
Binary files a/subprojects/docs/src/docs/release/content/Lato-regular.woff and /dev/null 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
deleted file mode 100644
index ee02337..0000000
--- a/subprojects/docs/src/docs/release/content/jquery-1.7.2-min.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/*! 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
deleted file mode 100644
index 8e349e0..0000000
Binary files a/subprojects/docs/src/docs/release/content/logo.gif and /dev/null differ
diff --git a/subprojects/docs/src/docs/release/content/style.css b/subprojects/docs/src/docs/release/content/style.css
deleted file mode 100644
index c194d08..0000000
--- a/subprojects/docs/src/docs/release/content/style.css
+++ /dev/null
@@ -1,177 +0,0 @@
-/* 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 {
-  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: 15px;
-  border: 1px dotted #999;
-  background-color: #FCFCFC;
-  border-radius: 5px;
-}
-
-section.minor-detail {
-  margin: 0 0.5em;
-  padding: 0 1em;
-}
-
-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;
-}
-
-pre {
-  border: 1px solid #B3B3B3;
-  border-radius: 5px;
-  padding: 0.5em;
-  line-height: 1.4;
-  margin-left: 1em;
-  margin-right: 1em;
-}
-
-pre.code {
-  background-color: white;
-}
-
-pre.tt {
-  background-color: #333;
-  color: white;
-}
-
-code, tt {
-  font-family: Monaco, "Courier New", Courier, monospace;
-  font-size: 80%;
-}
-
-.java_keyword {
-  color: #708;
-}
-.java_comment {
-  color: #A70;
-}
-.java_plain, .java_type {
-  color: black;
-}
-.java_separator {
-  color: #666;
-}
-.java_operator {
-  color: #A22;
-}
-.java_literal {
-  color: #281;
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index 37408a5..b824371 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,216 +1,368 @@
-For Gradle 1.1 our focus has been on usability improvements, bug fixes, and ground work to support the post 1.0 evolution of Gradle. 
+This is a significant release that includes exciting new features and some very important stabilization and optimization, particularly in the area of dependency management.
 
-We focussed on improving Maven integration, dependency management and OSGi support, while also knocking off a few other known issues. For more information, check the [full list of resolved tickets](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.1-rc-1%22%2C+%221.1-rc-2%22%29+ORDER+BY+priority&tempMax=1000). We did also manage to add some nice new features, outlined below.
+Our commitment to the Scala language as a first class citizen in the Gradle ecosystem is evident in this release. Through our hardwork, in cooperation with the folks from 
+[Typesafe](http://typesafe.com/), Gradle now ships with one of the most frequently requested improvements from Scala developers: decreased compile times through 
+[incremental compilation](#incremental-scala-compilation). The ability to [fork compilation](#scala-compilation-in-external-process) has also been added along with 
+improvements to [Eclipse integration for Gradle Scala projects](#improved-scala-ide-integration).
 
-You might be interested in our [recent posting](http://forums.gradle.org/gradle/topics/the_gradle_release_approach) of what you can expect from us regarding release frequency, backwards compatibility and our deprecation policy. We have also written a [quick outlook on the upcoming 1.2 release](http://forums.gradle.org/gradle/topics/gradle_1_2_release_outlook?rfm=1).
+As software becomes more and more modular and componentized, development teams are requiring more sophistication and more flexibility in how their built items are published for 
+downstream consumption. This release includes the [first step towards a richer and more powerful publication approach](#new-ivy-publishing-mechanism) within Gradle, that provides
+the ability to completely customize the Ivy module descriptor created when publishing in the Ivy format.
+
+Dependency management is a critical Gradle function. Many [important fixes/optimizations](#dependency-management-improvements) have been made in this release that improve the general
+dependency management experience in many areas. Along with these improvements, there is an extremely useful [new dependency report](#new-dependencyinsight-report-task) that can be
+used to extract precise information from the resolved dependency graph, with regard to a _particular_ dependency. This report is enabled by the powerful new 
+[Resolution Result API](http://gradle.org/docs/1.2/release-notes#dependency-resolution-result-api) introduced in Gradle 1.2, that has 
+been [further refined in this release](#resolution-result-api). You can expect to see further improvements and innovation in this area in future Gradle versions.
+
+As always, you can share your feedback and experiences with Gradle 1.3 via the [Gradle Forums](http://forums.gradle.org). Please read on for detailed information about this release. 
 
 ## New and noteworthy
 
-### Test Logging
+Here are the new features introduced in this release.
 
-Gradle 1.1 provides much more detailed information during test execution, right on the console. We've worked hard to make the the new output useful and informative out of the box, but we've also given you the ability to finely tune it to your liking.
+### New dependencyInsight report task
 
-The old output:
+The new `dependencyInsight` report complements the `dependencies` report (that was [improved in Gradle 1.2](http://gradle.org/docs/1.2/release-notes#improved-dependency-report)). Where 
+the `dependencies` report provides information on the whole dependency graph, the `dependencyInsight` report focusses on providing information on one (or more) dependencies within 
+the graph.
 
-<pre><tt>:spring-integration-twitter:test
-Test o.s.i.t.i.MessageSourceTests FAILED
-4 tests completed, 1 failure
-</tt>
-</pre>
+The report can be used to answer (very common) questions such as:
 
-The improved output:
+* Why is this dependency in the dependency graph?
+* Exactly which dependencies are pulling this dependency into the graph?
+* What is the actual version (i.e. *selected* version) of the dependency that will be used? 
+    * Is it the same as what was *requested*?
+* Why is the *selected* version of a dependency different to the *requested*?
 
-<pre><tt>:spring-integration-twitter:test
-o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit  FAILED
-    j.f.AssertionFailedError at MessageSourceTests.java:96
+The *selected* version of a dependency can be different to the *requested* (i.e. user declared) version due to dependency conflict resolution or by explicit dependency force rules. 
+Similar to the standard gradle depenency report, the `dependencyInsight` report shows both versions. It also shows a requested dynamic version (e.g. “`junit:junit:4.+`”) together 
+with the actually selected version (e.g. “`junit:junit:4.10`”). Please keep in mind that Maven snapshot dependencies are not treated as dynamic versions but as changing modules, 
+similar to what Maven does (for the difference see the [userguide](userguide/dependency_management.html#sec:dependency_management_overview)). Maven snapshots might be treated 
+as dynamic versions in a future version of Gradle which would provide nice insight into pom snapshot resolution.
 
-4 tests completed, 1 failed, 1 skipped
-</tt>
-</pre>
+The `dependencyInsight` report task is invaluable when investigating how and why a dependency is resolved, and it is available in all projects out of the box.
 
-#### Show Exceptions
+#### Example usage
 
-One of the most useful options is to show the exceptions thrown by failed tests. By default, Gradle will log a succinct message for every test exception. To get more detailed output, configure the `exceptionFormat`:
+Given the following build script:
 
-    test {
-        testLogging {
-            exceptionFormat "full"
-        }
+    apply plugin: 'java'
+
+    repositories {
+        maven { url "http://repo.gradle.org/gradle/repo" }
+    }
+
+    dependencies {
+        compile 'org.gradle:gradle-tooling-api:1.2'
+        compile 'org.slf4j:slf4j-api:1.6.5'
     }
 
-Which would produce output like:
+Let's find out about the `org.gradle:gradle-messaging` dependency:
 
-<pre><tt>o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit FAILED
-    j.f.AssertionFailedError: null
-        at j.f.Assert.fail(Assert.java:47)
-        at j.f.Assert.assertTrue(Assert.java:20)
-        at j.f.Assert.assertTrue(Assert.java:27)
-        at o.s.i.t.i.MessageSourceTests.testSearchReceivingMessageSourceInit(MessageSourceTests.java:96)
+<pre><tt>> gradle dependencyInsight --dependency org.gradle:gradle-messaging
+:dependencyInsight
+org.gradle:gradle-messaging:1.2
++--- org.gradle:gradle-tooling-api:1.2
+|    \--- compile
+\--- org.gradle:gradle-core:1.2
+     \--- org.gradle:gradle-tooling-api:1.2 (*)
 
-4 tests completed, 1 failed, 1 skipped
-</tt>
+(*) - dependencies omitted (listed previously)</tt>
 </pre>
 
-#### Stack Trace Filters
+Using this report, it is easy to see that the `gradle-messaging` dependency is being included transitively through `gradle-tooling-api` and `gradle-core` (which itself is a 
+dependency of `gradle-tooling-api`).
+
+Whereas the `dependencies` report shows the path from the top level dependencies down through the transitive dependencies, the `dependencyInsight` report shows the path from 
+a particular dependency to the dependencies that pulled it in. That is, it is an *inverted* view of the `dependencies` report.
+
+The `dependencyInsight` report is also useful for understanding the difference between *requested* and *selected* versions.  
+
+<pre><tt>> gradle dependencyInsight --dependency slf4j --configuration runtime
+:dependencyInsight
+org.slf4j:slf4j-api:1.6.6 (conflict resolution)
++--- org.gradle:gradle-tooling-api:1.2
+|    \--- runtime
++--- org.gradle:gradle-messaging:1.2
+|    +--- org.gradle:gradle-tooling-api:1.2 (*)
+|    \--- org.gradle:gradle-core:1.2
+|         \--- org.gradle:gradle-tooling-api:1.2 (*)
++--- org.gradle:gradle-base-services:1.2
+|    +--- org.gradle:gradle-tooling-api:1.2 (*)
+|    +--- org.gradle:gradle-messaging:1.2 (*)
+|    \--- org.gradle:gradle-core:1.2 (*)
+\--- org.gradle:gradle-core:1.2 (*)
+
+org.slf4j:slf4j-api:1.6.5 -> 1.6.6
+\--- runtime
+
+(*) - dependencies omitted (listed previously)</tt>
+</pre>
 
-Stack traces of test exceptions are automatically truncated not to show anything below the entry point into the test code. This filters out Gradle internals and internals of the test framework. A number of other filters are available. For example, when dealing with Groovy code it makes sense to add the `groovy` filter:
+Here we see that while `slf4j-api:1.6.5` was *requested*, `slf4j-api:1.6.6` was *selected* due to the conflict resolution. We can also see which dependency pulled in the slf4j.
 
-    test {
-        testLogging {
-            stackTraceFilters "truncate", "groovy"
-        }
-    }
+In this case, we were interested in the `runtime` dependency configuration. The default configuration the report uses is `compile`.
 
-While would produce output like:
-
-<pre><tt>o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit FAILED
-    o.s.i.MessageDeliveryException: Failed to send tweet 'Liking the new Gradle test logging output!'
-        at o.s.i.t.i.SearchReceivingMessageSource.<init>(SearchReceivingMessageSource.java:42)
-        at o.s.i.t.i.MessageSourceTests.testSearchReceivingMessageSourceInit(MessageSourceTests.java:81)
-        Caused by:
-        o.s.i.MessageSourceException: Oops! Looks like Twitter is down. Try again shortly.
-            at o.s.i.c.IntegrationObjectSupport.onInit(IntegrationObjectSupport.java:113)
-            at o.s.i.t.i.AbstractTwitterMessageSource.onInit(AbstractTwitterMessageSource.java:92)
-            at o.s.i.t.i.SearchReceivingMessageSource.<init>(SearchReceivingMessageSource.java:40)
-            ... 1 more
-</tt>
-</pre>
+For more information, see the [DependencyInsightReportTask documentation](dsl/org.gradle.api.tasks.diagnostics.DependencyInsightReportTask.html).
 
-#### Show Other Test Events
+### Incremental Scala compilation
 
-Besides a test having failed, a number of other test events can be logged:
+Due to the sophistication of the language and type system, Scala programs can take a long time to compile. Gradle 1.3 addresses this problem by
+integrating with [Zinc](https://github.com/typesafehub/zinc), a standalone version of [sbt](https://github.com/harrah/xsbt)'s incremental Scala
+compiler. By compiling only classes whose source code has changed since the previous compilation, and classes affected by these changes,
+Zinc can significantly reduce Scala compilation time. It is particularly effective when frequently compiling small code
+increments, as is often done at development time.
 
-    test {
-        testLogging {
-            events "started", "passed", "skipped", "failed", "standardOut", "standardError"
-            minGranularity 0
-        }
-    }
+To switch the `ScalaCompile` task from the default Ant based compiler to the new Zinc based compiler, use `scalaCompileOptions.useAnt = false`. 
+Except where noted in the [API documentation](dsl/org.gradle.api.tasks.scala.ScalaCompile.html), the Zinc based
+compiler supports exactly the same configuration options as the Ant based compiler.
 
-By setting `minGranularity`, these events aren't only shown for individual tests, but also for test classes and suites.
+Just like the Ant based compiler, the Zinc based compiler supports joint compilation of Java and Scala code. By default, all Java and Scala code
+under `src/main/scala` will participate in joint compilation. With the Zinc based compiler, even Java code will be compiled incrementally.
 
-#### Individual Logging Per Log Level
+To learn more about incremental Scala compilation, see the [Scala plugin](userguide/scala_plugin.html#N12A97) chapter in the Gradle User Guide.
 
-Test logging can be configured separately per log level:
+### Scala compilation in external process
 
-    test {
-        testLogging {
-            quiet {
-                events "failed"
-            }
-        }
-    }
+Scala compilation can now be performed outside the Gradle JVM in a dedicated compiler process, which can help to deal with memory issues. 
+
+External compilation is supported both for the existing Ant-based and the new Zinc-based Scala compiler. The API is very similar to that for Java and
+Groovy: [`ScalaCompile.fork = true`](dsl/org.gradle.api.tasks.scala.ScalaCompile.html#org.gradle.api.tasks.scala.ScalaCompile:fork)
+activates external compilation, and [`ScalaCompile.forkOptions`](dsl/org.gradle.api.tasks.scala.ScalaCompile.html#org.gradle.api.tasks.scala.ScalaCompile:forkOptions)
+allows to adjust memory settings.
+
+### Improved Scala IDE integration
+
+The [Eclipse Plugin](userguide/eclipse_plugin.html) now automatically excludes dependencies already provided by the
+ 'Scala Library' class path container. This improvement is essential for [Scala IDE](http://scala-ide.org) to work correctly. It also takes effect
+ when using the [Eclipse Gradle Integration](https://github.com/SpringSource/eclipse-integration-gradle).
+
+### Dependency management improvements
+
+With this release of Gradle, we have continued to improve our dependency management implementation. The focus for these improvements in Gradle 1.3 is on
+stability and this release includes a number of important fixes.
+
+#### Artifact cache stability
+
+This release fixes a number of issues that resulted in corruption of the artifact cache. In particular, Gradle will be much more stable in the case
+where you have many concurrent builds running on a machine, such as a CI build server, and these builds have dependencies that change frequently, such
+as Maven snapshots, or are using a very short cache expiry time.
+
+* [GRADLE-2544](http://issues.gradle.org/browse/GRADLE-2544) - Potential corruption of artifact cache on concurrent access.
+* [GRADLE-2458](http://issues.gradle.org/browse/GRADLE-2458) - Failures to store meta-data are silently ignored.
+* [GRADLE-2457](http://issues.gradle.org/browse/GRADLE-2457) - Potential corruption of artifact cache after a crash.
+
+#### Smarter handling of missing modules
+
+Gradle caches the fact that a given module is missing from a given repository. It uses this information to avoid unnecessary network requests when you
+are using multiple repositories, and when resolving dynamic versions. Previous versions of Gradle were overly keen in using this information, leading
+to problems where a misconfiguration would cause Gradle to decide that a certain module is missing and will be missing forever. In this release, Gradle
+uses a more sensible strategy to decide when to verify that something that it considers to be missing is, in fact, missing.
+
+* [GRADLE-2455](http://issues.gradle.org/browse/GRADLE-2455) - Smarter handling of missing module versions.
 
-On log levels `LIFECYCLE`, `INFO`, and `DEBUG`, some test events (most importantly failed tests) are already shown by default. For detailed documentation about all test logging related options, see [TestLogging](javadoc/org/gradle/api/tasks/testing/logging/TestLogging.html) and [TestLoggingContainer](javadoc/org/gradle/api/tasks/testing/logging/TestLoggingContainer.html).
+#### Ivy latest status improvements
 
-For further details, see the [forum announcement](http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging) for this feature.
+Previous Gradle releases had a number of issues resolving dynamic versions such as 'latest.integration' and we recently introduced some regressions in
+this area as we've attempted to fix these issues. We've reworked the implementation to simplify it internally and added many more integration tests,
+to avoid further regressions in the future.
 
-### Easier opening of test and code quality reports
+* [GRADLE-2502](http://issues.gradle.org/browse/GRADLE-2502) - Latest status resolution broken in Gradle 1.1.
+* [GRADLE-2504](http://issues.gradle.org/browse/GRADLE-2504) - NPE resolving dynamic versions from multiple repositories.
 
-When a test or code quality task has found a problem, it typically prints a message which includes the file path of the generated report. This message has been
-slightly changed to print the path as a file URL. Smart consoles recognize such URLs and make it easy to open them. For example, in Mac's Terminal.app reports are now
-just a CMD + double click away.
+#### Other fixes
 
-### Tooling API provides Gradle module information for external dependencies
+This release includes a number of other useful fixes:
 
-The Tooling API can be used to obtain the model of the project which includes the information about the dependencies of the project. In Gradle 1.1, the Tooling API also provides Gradle
-module information, i.e. group, name, version of each dependency.
+* [GRADLE-2547](http://issues.gradle.org/browse/GRADLE-2547) - Temporary files are left in $GRADLE_HOME/caches/artifacts-*/filestore.
+* [GRADLE-2543](http://issues.gradle.org/browse/GRADLE-2543) - Performance regression in local repository access.
+* [GRADLE-2486](http://issues.gradle.org/browse/GRADLE-2486) - Dependency resolution failures when running in parallel execution mode.
 
-Please see the javadoc for [GradleModuleVersion](javadoc/org/gradle/tooling/model/GradleModuleVersion.html). You can obtain the Gradle module information via [ExternalDependency.getGradleModuleVersion()](javadoc/org/gradle/tooling/model/ExternalDependency.html#getGradleModuleVersion\(\)).
+## Promoted features
 
-This feature helps the IDE integration like Gradle STS plugin to support
-more flexible developer workspace and hence faster dev cycles.
-Using Eclipse terms: it is a first step into providing the ability to toggle between
-Eclipse 'project' dependency and a regular 'binary' dependency.      
+Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to the backwards compatibility policy.
 
-### Global Maven settings.xml
+### Improved test logging APIs
 
-When migrating from Maven you can reuse the artifacts from your local Maven repository in your Gradle build. Gradle now honours the Maven settings located in <code><em>$M2_HOME</em>/conf/settings.xml</code> to locate the local Maven repository. If a local repository is defined in `~/.m2/settings.xml`, this location takes precedence over a repository definition in <code><em>$M2_HOME</em>/conf/settings.xml</code>. This is used when you have declared the `mavenLocal()` repository definition [...]
+[Gradle 1.1 introduced much improved test logging output](http://gradle.org/docs/1.1/release-notes#test-logging). The APIs that provided the ability to configure this logging
+were introduced in the incubating state. In this Gradle release, these APIs are being promoted and are no longer subject to change without notice.
 
-### Publishing SHA1 checksums to Ivy repositories
+For more information on the test logging functionality, see [this forum post](http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging) that introduced the feature.
 
-Gradle will now automatically generate and publish SHA1 checksum files when publishing to an Ivy repository. For each file `foo.ext` published, Gradle will also publish a checksum file with the name `foo.ext.sha1`. 
+## Fixed issues
 
-The presence of SHA1 checksums in the dependency repository allows Gradle to be more efficient when resolving dependencies. Recently Gradle gained the ability to use the checksum of a remote file to determine whether or not it truly needs to be downloaded. If Gradle can find a file with an identical checksum to the target remote file, it will use that instead of downloading the remote file. Also, for “changing” dependencies (e.g. snapshots) Gradle can compare the remote checksum with the [...]
+The list of issues fixed between 1.2 and 1.3 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.3-rc-1%22%2C+%221.3-rc-2%22%29+and+resolution+is+not+null+ORDER+BY+priority&tempMax=1000).
 
-Checksums have always been published to Maven repositories. This new feature of publishing checksums to Ivy repositories unlocks the recent Gradle dependency downloading optimizations to Ivy users. There is no change required to your build script to enable this functionality.
+## Incubating features
 
-### Dependency resolution supports HTTP Digest Authentication
+New features as typically introduced as “_incubating_”. The key characteristic of an incubating feature is that it may change in an incompatible way in a future Gradle release.
+At some time in the future, after incorporating invaluable real-world feedback from Gradle users, the feature will be deemed stable and “_promoted_”. At this point it is no longer subject to
+incompatible change. That is, the feature is now subject to the backwards compatibility policy.
 
-Due to the way Gradle used pre-emptive HTTP Authentication, Gradle 1.0 was not able to handle a repository secured with HTTP Digest Authentication. This issue has been resolved by using the following new strategy:
+Typically, the implementation quality of incubating features is sound. For some very challenging engineering problems, such as the Gradle Daemon or parallel task execution, it is 
+impossible to get the implementation quality right from the beginning as the feature needs real world exposure. The feedback from the incubation phase can be used to iteratively 
+improve the stability of the feature.
 
-* Any GET/HEAD request issued by Gradle will no longer contain pre-emptive HTTP Authentication headers.
-* An initial PUT/POST request will contain Basic Authentication headers for pre-emptive HTTP Authentication.
-    * If the server requires HTTP Basic Authentication, then this request will succeed automatically
-    * If the server requires HTTP Digest Authentication, then this request will fail with a 401, and we will re-send the request with the correct headers.
-* After the initial PUT/POST request, subsequent requests to the repository will have correct Auth headers, and will not require re-send.
+By releasing features early in the incubating state, users gain a competitive advantage through early access to new functionality in exchange for helping refine it over time, 
+ultimately making Gradle a better tool.
 
-## Upgrading from Gradle 1.0
+To learn more, see the [User Guide chapter on the Feature Lifecycle](userguide/feature_lifecycle.html).
 
-Please let us know if you encounter any issues during the upgrade to Gradle 1.1, that are not listed below.
+### New Ivy publishing mechanism
 
-### Deprecations
+This release introduces a new mechanism for publishing to Ivy repositories in the Ivy format. It also introduces some new general publishing constructs. This new publishing support is *incubating* and will co-exist with the [existing methods for publication](userguide/artifact_management.html) until the time where it supersedes it, at which point the old mechanism will 
+become deprecated. The functionality included in this release is the first step down the path of providing a better solution for sharing the artifacts built in your Gradle builds.
 
-#### Statement Labels
+In this release, we have focussed on laying the groundwork and providing the ability to modify the Ivy module descriptor that is published during a publish operation. It has long been possible
+to fully customise the `pom.xml` when publishing in the Maven format; it is now possible to do the same when publishing in the Ivy format.
 
-As in Java, statement labels are rarely used in Groovy. The following example shows a frequent pitfall where a statement label is erroneously used in an attempt to configure an object:
+#### The new 'ivy-publish' plugin
 
-    task foo {
-        dependsOn: bar
+The new functionality is provided by the '`ivy-publish`' plugin. In the simplest case, publishing using this plugin looks like…
+
+    apply plugin: "ivy-publish"
+    
+    // … declare dependencies and other config on how to build
+    
+    publishing {
+        repositories {
+            ivy {
+                url "http://mycompany.org/repo"
+            }
+        }
     }
 
-Whereas what the author actually intended was:
+To publish, you simply run the “`publish`” task.
+
+To modify the descriptor, you use a programmatic hook that modifies the descriptor content as XML. This is the same approach that you take in Gradle when modifying IDE metadata XML or Maven POM XML content.
 
-    task foo {
-        dependsOn bar
+    publishing {
+        publications {
+            ivy {
+                descriptor {
+                    withXml {
+                        asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
+                    }
+                }
+            }
+        }
     }
 
-Note the colon after `dependsOn` in the first code block. This extra colon causes the line to be interpreted as a statement label (a Java/Groovy language feature), which effectively makes it a non operation. Statement labels are not useful in Gradle build scripts.
+In this example we are modifying the version that is expressed of our `junit` dependency. With this hook, you can modify any aspect of the descriptor. You could for example easily build a functionality similar to Ivy deliver on top of this in conjunction with the new <a href="#resolution-result-api">Resolution Result API</a>. In general, it can be useful to optimize the descriptor for consumption instead of having it be an accurate record of how the module was built. 
+
+For more information on the new publishing mechanism, see the [new User Guide chapter](userguide/publishing_ivy.html).
+
+### Improved TestNG HTML report
+
+Gradle has long shipped with HTML report functionality for JUnit test results that improves on the Ant default. It is now possible to use the same HTML report format for TestNG test results.
+See the [reports generated by the Gradle automated builds](http://builds.gradle.org/repository/download/bt9/.lastSuccessful/reports/ide/integTest/index.html) as an example of the new improved report.
+
+The reports are not yet turned on by default. To enable the new TestNG test reports, simply set 
+[testReport = true](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) on your test task.
+
+Note: The new report might exhibit increased heap usage for tests that log many messages to the standard streams. You can increase the heap allocated to the test process via the 
+[jvmArgs property](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:jvmArgs) of the test task.
+
+#### Improvement details
+
+* The html report is easier to read and browse than the standard TestNG report
+* Both reports, xml (for CI) and html (for you), contain the test output (i.e. messages logged to the standard streams or via the standard logging toolkits). This is extremely useful for debugging certain test failures.
+* The reports neatly work with Gradle parallel testing ([test.maxParallelForks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks)) and forking features ([test.forkEvery](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:forkEvery)). The standard TestNG reports are not compatible with Gradle's parallel test execution and can contain confusing or incorrect information.
+
+## Deprecations
 
-To prevent such mistakes that are hard to track down and debug, the usage of statement labels in build scripts has been deprecated and Gradle will issue a deprecation warning when they are used.
-In Gradle 2.0, statement labels in build scripts will no longer be supported and will cause an error.
+### Ant-task based Java compiler integration
 
-#### M2_HOME system property
+Gradle currently supports two different Java compiler integrations: A native Gradle integration that uses the compiler APIs directly, and an Ant-task
+based implementation that uses the `<javac>` Ant task. The native Gradle integration has been the default since Gradle 1.0-milestone-9. Some of its advantages are:
+
+* Faster compilation
+* Can run in Gradle compiler daemon (external process that is reused between tasks)
+* More amenable to future enhancements
+
+The Ant-task based integration has now been deprecated and will be removed in Gradle 2.0. As a result, the following properties of `CompileOptions` are also
+deprecated and will be removed in Gradle 2.0:
+
+* `useAnt`
+* `optimize`
+* `includeJavaRuntime`
+
+### Ant-task based Groovy compiler integration
+
+Gradle currently supports two different Groovy compiler integrations: A native Gradle integration that uses the compiler APIs directly, and an Ant-task
+based implementation that uses the `<groovyc>` Ant task. The native Gradle integration has been the default since Gradle 1.0. Some of its advantages are:
+
+* Faster compilation
+* Correct handling of AST transformations
+* Can run in Gradle compiler daemon (external process that is reused between tasks)
+* More amenable to future enhancements
+
+The Ant-task based integration has now been deprecated and will be removed in Gradle 2.0. As a result, the following properties of `GroovyCompileOptions` are also
+deprecated and will be removed in Gradle 2.0:
+
+* `useAnt`
+* `stacktrace`
+* `includeJavaRuntime`
+
+### Changing the name of a repository once added to a repository container
+
+The [`ArtifactRepository`](javadoc/org/gradle/api/artifacts/repositories/ArtifactRepository.html) type has a `setName(String)` method that you
+could use to change the repository name after it has been created. Doing so has been deprecated. The name of the repository should be specified at creation time via the DSL.
+
+For example:
+
+    repositories {
+        ivy {
+            name "my-ivy-repo"
+        }
+    }
+    
+    // The following is now deprecated
+    repositories.ivy.name = "changed-name"
 
-Previously, Gradle looked for a JVM system property named `M2_HOME` for the location of a custom Maven home directory. This has been deprecated in favor of an environment variable, named `M2_HOME`, which is also used by other tools that integrate with Maven.
-Support for the `M2_HOME` system property will be removed in Gradle 2.0.
+A deprecation warning will be issued if a name change is attempted. It has been deprecated because changing the name of a repository after it has been added to the 
+container can cause problems when tasks are automatically created for created repositories.
 
-#### Publication of missing artifacts
+## Changes to existing incubating features
 
-Previously, if an artifact to be published referred to a file that does not exist during publication, then Gradle would silently ignore the artifact to be published. This is only likely to occur when declaring [file artifacts](userguide/artifact_management.html#N143A6).
+### Resolution result API
 
-This behavior is now deprecated. Attempting to publish a non-existant artifact file will result in a deprecation warning, and will produce an error in Gradle 2.0.
+The entry point to the ResolutionResult API has changed. You now access the ResolutionResult via [configuration.incoming.resolutionResult](dsl/org.gradle.api.artifacts.Configuration.html#org.gradle.api.artifacts.Configuration:incoming).
+New convenience methods were also added to the API. For more information please refer to the javadoc for [ResolutionResult](javadoc/org/gradle/api/artifacts/result/ResolutionResult.html).
 
-#### DSL
+### Incubating C++ `Compile` task type removed
 
-##### `Project.fileTree(Object)` - Removal of incorrect `@deprecation` tag
+This was replaced by `CppCompile` in Gradle 1.2. You should use the replacement class instead.
 
-The `Project.fileTree(Object)` method was incorrectly annotated with the `@deprecated` Javadoc tag in Gradle 1.0-milestone-8. This method has not been deprecated and the Javadoc tag has been removed.
+### Incubating `GppCompileSpec` properties removed
 
-##### `Project.fileTree(Closure)` - Addition of `@deprecation` tag
+The deprecated `task` property was removed from `GppCompileSpec`.
 
-The `Project.fileTree(Closure)` method was deprecated in Gradle 1.0-milestone-8. The method was not annotated with the `@deprecated` javadoc tag at that time. This has been added for this release.
-This method will be removed in Gradle 2.0.
+## Potential breaking changes
 
-#### API
+### The behavior of [Test.testReport](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) property for TestNG
 
-##### `org.gradle.api.tasks.testing.TestLogging` - Moved into `logging` subpackage
+The default value of the [testReport property value of Test tasks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) has been changed to `false` for TestNG. Previously, this property was ignored when running with TestNG - the html results were generated regardless.
 
-The `org.gradle.api.tasks.testing.TestLogging` interface was moved into package `org.gradle.api.tasks.testing.logging` (and subsequently enhanced with new methods). For backwards compatibility reasons, the old interface was kept at its original location,
-but is now deprecated. The old interface will be removed in Gradle 2.0.
+This property now enables/disables the new [improved TestNG report introduced in this release](#improved-testng-html-report).
 
-### Potential breaking changes
+### Removed GraphvizReportRenderer
 
-##### `idea.project.jdkName`
+This was an early contribution that did not work and was an undocumented private type.
 
-We've decided to change the IDEA plugin's default JDK name. The new default is now smarter. Without this change, many users had to configure the JDK name explicitly in the builds or manually tweak the JDK name in IDEA after running the `gradle idea` task. The current default uses the Java version that Gradle runs with.
+### Removed `org.gradle.api.publication` package
 
-Although we believe the new default is much better for majority of users, there might be some builds out there that preferred the old default. If you happen to prefer the old default (`1.6`) please configure that explicitly in your build via [idea.project.jdkName](dsl/org.gradle.plugins.ide.idea.model.IdeaProject.html#org.gradle.plugins.ide.idea.model.IdeaProject:jdkName)
+This package contained some early experiments in a new publication model. It was incomplete and undocumented. It is superseded by the new [publishing functionality introduced in this release](#new-ivy-publishing-mechanism), so has been removed.
 
-##### `AbstractTask.getDynamicObjectHelper()`
+## Community contributions
 
-Deprecated internal method `AbstractTask.getDynamicObjectHelper()` has been removed.
+We would like to thank the following community members for making contributions to this release of Gradle.
 
-## Fixed Issues
+* Matt Khan - fixed resetting of `test.debug` to `false` when using `test.jvmArgs`  (GRADLE-2485)
+* Gerd Aschemann - fixes for `application` plugins generated shell scripts (GRADLE-2501)
+* Cruz Fernandez - fixes to the properties sample project
+* Fadeev Alexandr - fixes for Gradle Daemon on Win 7 when `PATH` env var is not set (GRADLE-2461)
+* Ben Manes - improved Scala IDE integration ([pull request #99](https://github.com/gradle/gradle/pull/99))
 
-The list of issues fixed between 1.0 and 1.1 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.1-rc-1%22%2C+%221.1-rc-2%22%29+ORDER+BY+priority&tempMax=1000).
+We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/stylesheets/dslHtml.xsl b/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
index 555afb4..b232faa 100644
--- a/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
+++ b/subprojects/docs/src/docs/stylesheets/dslHtml.xsl
@@ -37,7 +37,7 @@
     <!-- customise the stylesheets to add to the <head> element -->
     <xsl:template name="output.html.stylesheets">
         <link href="base.css" rel="stylesheet" type="text/css"/>
-        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="docs.css" rel="stylesheet" type="text/css"/>
         <link href="dsl.css" rel="stylesheet" type="text/css"/>
     </xsl:template>
 
diff --git a/subprojects/docs/src/docs/stylesheets/standaloneHtml.xsl b/subprojects/docs/src/docs/stylesheets/standaloneHtml.xsl
index 8c07186..53e75ae 100644
--- a/subprojects/docs/src/docs/stylesheets/standaloneHtml.xsl
+++ b/subprojects/docs/src/docs/stylesheets/standaloneHtml.xsl
@@ -32,7 +32,7 @@
     <xsl:template name="user.head.content">
         <style type="text/css">
             <xi:include href="base.css" parse="text"/>
-            <xi:include href="style.css" parse="text"/>
+            <xi:include href="docs.css" parse="text"/>
             <xi:include href="userguide.css" parse="text"/>
         </style>
     </xsl:template>
diff --git a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
index dbd1b2e..e8ea7e8 100644
--- a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
+++ b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
@@ -34,7 +34,7 @@
     <xsl:param name="html.stylesheet">DUMMY</xsl:param>
     <xsl:template name="output.html.stylesheets">
         <link href="base.css" rel="stylesheet" type="text/css"/>
-        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="docs.css" rel="stylesheet" type="text/css"/>
         <link href="userguide.css" rel="stylesheet" type="text/css"/>
     </xsl:template>
 
diff --git a/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl b/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
index 351d486..002ea21 100644
--- a/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
+++ b/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
@@ -25,7 +25,7 @@
 
     <xsl:template name="output.html.stylesheets">
         <link href="base.css" rel="stylesheet" type="text/css"/>
-        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="docs.css" rel="stylesheet" type="text/css"/>
         <link href="userguide.css" rel="stylesheet" type="text/css"/>
         <link href="print.css" rel="stylesheet" type="text/css" media="print"/>
     </xsl:template>
diff --git a/subprojects/docs/src/docs/userguide/artifactMngmt.xml b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
index 8d5dded..af99029 100644
--- a/subprojects/docs/src/docs/userguide/artifactMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
@@ -83,7 +83,13 @@
         </sample>
         <para>As you can see, you can either use a reference to an existing repository or create a new repository.
         As described in <xref linkend="sub:more_about_ivy_resolvers"/>, you can use all the Ivy resolvers suitable
-        for the purpose of uploading.</para>
+        for the purpose of uploading.
+        </para>
+        <para>If an upload repository is defined with multiple patterns, Gradle must choose a pattern to use for uploading each file.
+            By default, Gradle will upload to the pattern defined by the <literal>url</literal> parameter, combined with the optional <literal>layout</literal> parameter.
+            If no <literal>url</literal> parameter is supplied, then Gradle will use the first defined <literal>artifactPattern</literal> for uploading,
+            or the first defined <literal>ivyPattern</literal> for uploading ivy files, if this is set.
+        </para>
         <para>Uploading to a Maven repository is described in <xref linkend="uploading_to_maven_repositories"/>.</para>
     </section>
     <section id="project_libraries">
diff --git a/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml b/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
new file mode 100644
index 0000000..0f34fc2
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
@@ -0,0 +1,88 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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='bootstrap_plugin'>
+    <title>Bootstrap Plugin</title>
+    <para>The Gradle bootstrap plugin prepares the current project for Gradle.
+        Typically it will create the relevant build.gradle, settings.gradle files.
+        At the moment only conversion from maven3 is supported.</para>
+    <para>
+        The plugin is currently *incubating* which means it is already useful
+        but not everything might work perfectly.
+        The api, plugin and task names may change before the final release.
+        Please let us know your feedback or report any issues.</para>
+    <para>
+        The plugin works by obtaining the effective pom of the current project
+        by executing external 'mvn' command. Then it reads the dependencies
+        and other information to generate build.gradle scripts.</para>
+    <para>
+        The plugin is inspired by the <ulink url="https://github.com/jbaruch/maven2gradle">maven2gradle tool</ulink>
+        founded and maintained by recognized leaders of Gradle community;
+        created by Baruch Sadogursky with contributions from Antony Stubbs, Matthew McCullough and others.
+    </para>
+
+    <section>
+        <title>Maven conversion - features</title>
+        <itemizedlist>
+            <listitem>Uses effective pom and effective settings
+            (support for pom inheritance, dependency management, properties)</listitem>
+            <listitem>Supports both single module and multimodule projects.
+                Generates settings.gradle for multimodule projects (*).</listitem>
+            <listitem>Supports custom module names (that differ from directory names)</listitem>
+            <listitem>Generates general metadata - id, description and version</listitem>
+            <listitem>Applies maven, java and war plugins (as needed)</listitem>
+            <listitem>Supports packaging war projects as jars if needed</listitem>
+            <listitem>Generates dependencies (both external and inter-module)</listitem>
+            <listitem>Generates download repositories (inc. local maven repository)</listitem>
+            <listitem>Adjusts java compiler settings</listitem>
+            <listitem>Supports packaging of sources and tests</listitem>
+            <listitem>Supports testng runner</listitem>
+            <listitem>Generates global exclusions from Maven enforcer plugin settings</listitem>
+        </itemizedlist>
+
+        <para>
+            (*) - Note: Your project will be considered multi-module
+            only if your reactor is also a parent of at least one of your modules.
+            Why so? Reactor project is built last, when Parent project is built first.
+            The reactor has to be built first, because effective-pom Mojo generates needed output
+            only if it finds modules in first project it encounters.
+            Making reactor also a parent achieves this.
+        </para>
+    </section>
+
+    <section>
+        <title>Usage</title>
+        <para>To convert a maven project follow the steps:</para>
+        <itemizedlist>
+            <listitem>Make sure your maven project builds and uses maven3.</listitem>
+            <listitem>Make sure <code>mvn</code> command can be executed and it runs maven3.</listitem>
+            <listitem>Create <filename>build.gradle</filename> file in the root folder of your maven project.</listitem>
+            <listitem>Specify <code>apply plugin: 'maven2Gradle'</code> and nothing else
+                in the <filename>build.gradle</filename> file.</listitem>
+            <listitem>Make sure you are using the Gradle version that contains the plugin.
+                If necessary download the required Gradle version.
+                Until Gradle 1.2 is released you should use the
+                <ulink url="website:nightly">nightly build</ulink>.
+                You only need this version for conversion of the maven project.
+                When converting is complete feel free to use the desired Gradle version, for example 1.1.
+            </listitem>
+            <listitem>Run <code>gradle tasks</code>. You should see <code>maven2Gradle</code> task available.</listitem>
+            <listitem>Run <code>gradle maven2Gradle</code>.</listitem>
+            <listitem>Advanced users: you can configure following boolean properties on the <code>maven2Gradle</code> task:
+                <code>verbose</code> (shows more output, including the effective pom)
+                and <code>keepFile</code> (keeps the obtained effective pom file).</listitem>
+        </itemizedlist>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
index b44b974..ade0fda 100644
--- a/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
@@ -2,7 +2,7 @@
     <title>The Build Announcements Plugin</title>
     <note>
         <para>
-            The build announcements is experimental and should not be considered stable.
+            The build announcements is incubating 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>
diff --git a/subprojects/docs/src/docs/userguide/buildEnvironment.xml b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
index 937c201..3ca5172 100644
--- a/subprojects/docs/src/docs/userguide/buildEnvironment.xml
+++ b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
@@ -20,7 +20,7 @@
         <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
+            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.
diff --git a/subprojects/docs/src/docs/userguide/commandLine.xml b/subprojects/docs/src/docs/userguide/commandLine.xml
index 5ebe5b5..35f69e5 100644
--- a/subprojects/docs/src/docs/userguide/commandLine.xml
+++ b/subprojects/docs/src/docs/userguide/commandLine.xml
@@ -156,6 +156,28 @@
         </varlistentry>
         <varlistentry>
             <term>
+                <option>--parallel</option>
+            </term>
+            <listitem>
+                <para>
+                    Build projects in parallel. Gradle will attempt to determine the optimal number of executor threads to use.
+                    This option should only be used with decoupled projects (see <xref linkend="sec:decoupled_projects"/>).
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term>
+                <option>--parallel-threads</option>
+            </term>
+            <listitem>
+                <para>
+                    Build projects in parallel, using the specified number of executor threads. For example<literal>--parallel-threads=3</literal>.
+                    This option should only be used with decoupled projects (see <xref linkend="sec:decoupled_projects"/>).
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term>
                 <option>--profile</option>
             </term>
             <listitem>
diff --git a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
index 9042d35..7381d8c 100644
--- a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
@@ -59,6 +59,19 @@
             <literal>compile</literal>, are still executed.</para>
     </section>
 
+    <section id="sec:continue_build_on_failure">
+        <title>Continuing the build when a failure occurs</title>
+        <para>By default, Gradle will abort execution and fail the build as soon as any task fails. This allows the build to complete sooner, but hides other failures
+            that would have occurred. In order to discover as many failures as possible in a single build execution, you can use the <literal>--continue</literal> option.
+        </para>
+        <para>When executed with <literal>--continue</literal>, Gradle will execute <emphasis>every</emphasis> task to be executed where all of the dependencies for that task completed without failure,
+            instead of stopping as soon as the first failure is encountered. Each of the encountered failures will be reported at the end of the build.
+        </para>
+        <para>If a task fails, any subsequent tasks that were depending on it will not be executed, as it is not safe to do so. For example, tests will not
+            run if there is a compilation failure in the code under test; because the test task will depend on the compilation task (either directly or indirectly).
+        </para>
+    </section>
+
     <section>
         <title>Task name abbreviation</title>
         <para>When you specify tasks on the command-line, you don't have to provide the full name of the task. You only need to provide enough of the
@@ -152,6 +165,30 @@
                 <output args="-q dependencies api:dependencies webapp:dependencies"/>
             </sample>
         </section>
+        <section id="sec:dependency_insight">
+            <title>Getting the insight into a particular dependency</title>
+            <para id="para:commandline_dependency_insight_report">Running <userinput>gradle dependencyInsight</userinput>
+                gives you an insight into a particular dependency (or dependencies) that match specified input.
+                Below is an example of this report:
+            </para>
+            <sample id="dependencyInsightReport" dir="userguide/tutorial/projectReports" title="Getting the insight into a particular dependency">
+                <output args="-q webapp:dependencyInsight --dependency groovy --configuration compile"/>
+            </sample>
+            <para>
+                This task is extremely useful for investigating the dependency resolution,
+                finding out where certain dependencies are coming from and why certain versions are selected.
+                For more information please see <apilink class="org.gradle.api.tasks.diagnostics.DependencyInsightReportTask"/>.
+            </para>
+            <para>
+                The built-in dependencyInsight task is a part of the 'Help' tasks group.
+                The task needs to configured with the dependency and the configuration.
+                The report looks for the dependencies that match the specified dependency spec in the specified configuration.
+                If java related plugin is applied, the dependencyInsight task is pre-configured with 'compile' configuration because typically it's the compile dependencies we are interested in.
+                You should specify the dependency you are interested in via the command line '--dependency' option.
+                If you don't like the defaults you may select the configuration via '--configuration' option.
+                For more information see <apilink class="org.gradle.api.tasks.diagnostics.DependencyInsightReportTask"/>.
+            </para>
+        </section>
         <section id="sec:listing_properties">
             <title>Listing project properties</title>
             <para>Running <userinput>gradle properties</userinput> gives you a list of the properties of the selected
diff --git a/subprojects/docs/src/docs/userguide/comparingBuilds.xml b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
new file mode 100644
index 0000000..3326eca
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
@@ -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.
+  -->
+
+<chapter id="comparing_builds">
+    <title>Comparing Builds</title>
+    <note>
+        <para>
+            Build comparison support is an <firstterm>incubating</firstterm> feature. This means that it is incomplete and not yet at regular Gradle production quality.
+            This also means that this Gradle User Guide chapter is a work in progress.
+        </para>
+    </note>
+    <para>
+        Gradle provides support for comparing the <firstterm>outcomes</firstterm> (e.g. the produced binary archives) of two builds.
+        There are several reasons why you may want to compare the outcomes of two builds. You may want to compare:
+    </para>
+    <itemizedlist>
+        <listitem>
+            <para>A build with a newer version of Gradle than it's currently using (i.e. upgrading the Gradle version).</para>
+        </listitem>
+        <listitem>
+            <para>A Gradle build with a build executed by another tool such as Apache Ant, Apache Maven or something else (i.e. migrating to Gradle).</para>
+        </listitem>
+        <listitem>
+            <para>The same Gradle build, with the same version, before and after a change to the build (i.e. testing build changes).</para>
+        </listitem>
+    </itemizedlist>
+    <para>
+        By comparing builds in these scenarios you can make an informed decision about the Gradle upgrade, migration to Gradle or build change by understanding the differences in the outcomes.
+        The comparison process produces a HTML report outlining which outcomes were found to be identical and identifying the differences between non-identical outcomes.
+    </para>
+    <section>
+        <title>Definition of terms</title>
+        <para>The following are the terms used for build comparison and their definitions.</para>
+        <variablelist>
+            <varlistentry>
+                <term>“Build”</term>
+                <listitem>
+                    <para>
+                        In the context of build comparison, a build is not necessarily a Gradle build.
+                        It can be any invokable “process” that produces observable “outcomes”.
+                        At least one of the builds in a comparison will be a Gradle build.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Build Outcome”</term>
+                <listitem>
+                    <para>
+                        Something that happens in an observable manner during a build, such as the creation of a zip file or test execution.
+                        These are the things that are compared.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Source Build”</term>
+                <listitem>
+                    <para>
+                        The build that comparisons are being made against, typically the build in its “current” state.
+                        In other words, the left hand side of the comparison.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Target Build”</term>
+                <listitem>
+                    <para>
+                        The build that is being compared to the source build, typically the “proposed” build.
+                        In other words, the right hand side of the comparison.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Host Build”</term>
+                <listitem>
+                    <para>
+                        The Gradle build that executes the comparison process. It may be the same project as either the “target” or “source” build
+                        or may be a completely separate project. It does not need to be the same Gradle version as the “source” or “target” builds.
+                        The host build must be run with Gradle 1.2 or newer.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Compared Build Outcome”</term>
+                <listitem>
+                    <para>
+                        Build outcomes that are intended to be logically equivalent in the “source” and “target” builds,
+                        and are therefore meaningfully comparable.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Uncompared Build Outcome”</term>
+                <listitem>
+                    <para>
+                        A build outcome is uncompared if a logical equivalent from the other build cannot be found
+                        (e.g. a build produces a zip file that the other build does not).
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>“Unknown Build Outcome”</term>
+                <listitem>
+                    <para>
+                        A build outcome that cannot be understood by the host build. This can occur when the source or target build
+                        is a newer Gradle version than the host build and that Gradle version exposes new outcome types. Unknown build outcomes
+                        can be compared in so far as they can be identified to be logically equivalent to an unknown build outcome in the other build,
+                        but no meaningful comparison of what the build outcome actually is can be performed. Using the latest Gradle version
+                        for the host build will avoid encountering unknown build outcomes.
+                    </para>
+                </listitem>
+            </varlistentry>
+
+        </variablelist>
+    </section>
+    <section>
+        <title>Current Capabilities</title>
+        <para>
+            As this is an <firstterm>incubating</firstterm> feature, a limited set of the eventual functionality has been implemented at this time.
+        </para>
+        <section>
+            <title>Supported builds</title>
+            <para>
+                Only support for executing Gradle builds is available at this time.
+                Source and target build must execute with Gradle newer or equal to <literal>1.0</literal>.
+                Host build must be at least <literal>1.2</literal>.
+            </para>
+            <para>
+                Future versions will provide support for executing builds from other build systems such as Apache Ant
+                or Apache Maven, as well as support for executing arbitrary processes (e.g. shell script based builds)
+            </para>
+        </section>
+        <section>
+            <title>Supported build outcomes</title>
+            <para>
+                Only support for comparing build outcomes that are <literal>zip</literal> archives is supported at this time.
+                This includes <literal>jar</literal>, <literal>war</literal> and <literal>ear</literal> archives.
+            </para>
+            <para>
+                Future versions will provide support for comparing outcomes such as test execution (i.e. which tests were executed, which tests failed etc.)
+            </para>
+        </section>
+    </section>
+    <section>
+        <title>Comparing Gradle Builds</title>
+        <para>
+            The <literal>compare-gradle-builds</literal> plugin can be used to facilitate a comparison between two Gradle builds. The plugin
+            adds a <apilink class="org.gradle.api.plugins.buildcomparison.gradle.CompareGradleBuilds"/> task named “<literal>compareGradleBuilds</literal>”
+            to the project. The configuration of this task specifies what is to be compared. By default, it is configured to compare the current build with itself
+            using the current Gradle version by executing the tasks: “<literal>clean assemble</literal>”.
+        </para>
+        <programlisting>
+            apply plugin: 'compare-gradle-builds'
+        </programlisting>
+        <para>
+            This task can be configured to change what is compared.
+        </para>
+        <programlisting>
+            compareGradleBuilds {
+    sourceBuild {
+        projectDir "/projects/project-a"
+        gradleVersion "1.1"
+    }
+    targetBuild {
+        projectDir "/projects/project-b"
+        gradleVersion "1.2"
+    }
+}
+        </programlisting>
+        <para>
+            The above example configures a comparison between two different projects using two different Gradle versions.
+        </para>
+        <section>
+            <title>Trying Gradle upgrades</title>
+            <para>
+                You can use the build comparison functionality to very quickly try a new Gradle version with your build.
+            </para>
+            <para>
+                To try your current build with a different Gradle version, simply add the following to the <filename>build.gradle</filename> of the <firstterm>root project</firstterm>.
+            </para>
+            <programlisting>
+apply plugin: 'compare-gradle-builds'
+
+compareGradleBuilds {
+    targetBuild.gradleVersion = "«gradle version»"
+}
+            </programlisting>
+            <para>
+                Then simply execute the <command>compareGradleBuilds</command> task. You will see the console output of the “source” and “target” builds as they are executing.
+            </para>
+        </section>
+        <section>
+            <title>The comparison “result”</title>
+            <para>
+                If there are any differences between the <firstterm>compared outcomes</firstterm>, the task will fail. The location of the HTML report
+                providing insight into the comparison will be given. If all compared outcomes are found to be identical, and there are no uncompared outcomes,
+                and there are no unknown build outcomes the task will succeed.
+            </para>
+            <para>
+                You can configure the task to not fail on compared outcome differences by setting the <literal>ignoreFailures</literal> property to true.
+            </para>
+            <programlisting>
+compareGradleBuilds {
+    ignoreFailures = true
+}
+            </programlisting>
+        </section>
+        <section>
+            <title>Which archives are compared?</title>
+            <para>
+                For an archive to be a candidate for comparison, it must be added as an artifact of the archives configuration.
+                Take a look at <xref linkend="artifact_management"/> for more information on how to configure and add artifacts.
+            </para>
+            <para>
+                The archive must also have been produced by a <apilink class="org.gradle.api.tasks.bundling.Zip" />, <apilink class="org.gradle.api.tasks.bundling.Jar" />,
+                <apilink class="org.gradle.api.tasks.bundling.War" />, <apilink class="org.gradle.plugins.ear.Ear" /> task. Future versions of Gradle
+                will support increased flexibility in this area.
+            </para>
+        </section>
+
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index aae9839..dbf8929 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -17,56 +17,53 @@
     <title>Dependency Management</title>
     <section id='sec:Introduction'>
         <title>Introduction</title>
-        <para>This chapter gives an overview of issues related with dependency management and presents how Gradle can be used to overcome them.
-        </para>
         <para>Gradle offers a very good support for dependency management. If you are familiar with Maven or Ivy approach you will be delighted to learn that:
         <itemizedlist>
             <listitem>
-                <para>All the concepts that you already know and like are still there and are fully supported by Gradle. The current dependency management solutions all require to work with XML descriptor files and are usually
-        based on remote repositories for downloading the dependencies. Gradle fully supports this approach.
+                <para>Gradle fully supports transitive dependency management. Gradle also works <emphasis>perfectly</emphasis> with your existent dependency management
+                    infrastructure, be it Maven or Ivy. All the repositories you have set up with your custom POM or
+                    ivy files can be used as they are. No changes necessary.
                 </para>
             </listitem>
             <listitem>
-                <para>Gradle works <emphasis>perfectly</emphasis> with your existent dependency management
-                    infrastructure, be it Maven or Ivy. All the repositories you have set up with your custom POM or
-                    ivy files can be used as they are. No changes necessary.
+                <para>If you don't use transitive dependency management and your external libraries live just as files in version control or on some shared drive, Gradle provides powerful functionality
+                    to support this.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Gradle provides an additional, optional support for transitive dependency management that is not based on XML descriptor files called Module Dependencies, where you describe
+                    the dependency hierarchy in the build script.
                 </para>
             </listitem>
             <listitem>
-                <para>Additionally, Gradle offers a simpler approach, which might be better suited for some projects.
+                <para>The job of a build system is to support all major patterns for how people deal with dependencies, not to force people in a certain way of doing things. In particular for migration scenarios
+                    it is extremely important that any current approach is supported so that you can use the same input structure in the new evolving Gradle build than in the existing build as long as it
+                    is in production. That enables you to compare the results. Gradle is extremely flexible. So even if your project is using a custom dependency management or say an Eclipse .classpath file as master data for dependency management,
+                    it would be very easy to write a little adaptor plugin to use this data in Gradle. For migration purposes this is a common technique with Gradle. Once you have migrated, it might be a good idea though not to
+                    use a .classpath file for dependency metadata any longer :).
                 </para>
             </listitem>
         </itemizedlist>
         </para>
     </section>
     <section id='sec:dependency_management_overview'>
-        <title>Dependency management overview</title>
-        <para>We think dependency management is very important for almost any project. Yet the kind of dependency
+        <title>Dependency Management Best Practices.</title>
+        <para>We have an opinion on what are dependency management best practices. As usual, Gradle does not force our opinion onto you, but supports any kind of pattern you want to use. Nonetheless
+            we would like to share our opinion.
+        </para>
+        <para>We think good dependency management is very important for almost any project. Yet the kind of dependency
             management you need depends on the complexity and the environment of your project. Is your project a
             distribution or a library? Is it part of an enterprise environment, where it is integrated into other
-            projects builds or not? But all types of projects share the following requirements:
+            projects builds or not? But all types of projects should follow the rules below:
         </para>
-        <itemizedlist>
-            <listitem>
-                <para>The version of the jar must be easy to recognize. Sometimes the version is in the Manifest file of
-                    the jar, often not. And even if, it is rather painful to always look into the Manifest file to learn
-                    about the version. Therefore we think that you should only use jars which have their version as part
-                    of their file name.
-                </para>
-            </listitem>
-            <listitem>
-                <para>It hopes to be clear what are the first level dependencies and what are the transitive ones. There
-                    are different ways to achieve this. We will look at this later.
-                </para>
-            </listitem>
-            <listitem>
-                <para>Conflicting versions of the same jar should be detected and either resolved or cause an exception.
-                </para>
-            </listitem>
-        </itemizedlist>
         <section id='sub:versioning_the_jar_name'>
             <title>Versioning the jar name</title>
-            <para>Why do we think this is necessary? Without a dependency management as described above, your are likely
+            <para>The version of the jar must be easy to recognize. Sometimes the version is in the Manifest file of
+                the jar, often not. And even if, it is rather painful to always look into the Manifest file to learn
+                about the version. Therefore we think that you should only use jars which have their version as part
+                of their file name. If you are using transitive dependency management you are forced to do this in any case.
+            </para>
+            <para>Why do we think this is important? Without a dependency management as described above, your are likely
                 to burn your fingers sooner or later. If it is unclear which version of a jar your are using, this can
                 introduce subtle bugs which are very hard to find. For example there might be a project which uses
                 Hibernate 3.0.4. There are some problems with Hibernate so a developer installs version 3.0.5 of
@@ -77,32 +74,41 @@
             </para>
         </section>
         <section id='sub:transitive_dependency_management'>
-            <title>Transitive dependency management</title>
+            <title>Use some form of transitive dependency management</title>
+            <para>When we talk about transitive dependency management, we mean any technique that enables to distinguish
+                between what are the first level dependencies and what are the transitive ones. We will about different techniques for this later on.
+            </para>
             <para>Why is transitive dependency management so important? If you don't know which dependencies are first
-                level dependencies and which ones are transitive you will soon lose control over your build. Even
-                Gradle has already 20+ jars. An enterprise project using Spring, Hibernate, etc. easily ends up with
-                100+ jars. There is no way to memorize where all these jars come from. If you want to get rid of a first
+                level dependencies and which ones are transitive you will soon lose control over your build. Even a non enterprise project
+                Gradle has already 100+ jars. An enterprise project using Spring, Hibernate, etc. easily ends up with
+                many more jars. There is no way to memorize where all these jars come from. If you want to get rid of a first
                 level dependency you can't be sure which other jars you should remove. Because a dependency of a
                 first level dependency might also be a first level dependency itself. Or it might be a transitive
                 dependency of another of your first level dependencies. Many first level dependencies are runtime
                 dependencies and the transitive dependencies are of course all runtime dependencies. So the compiler
                 won't help you much here. The end of the story is, as we have seen very often, no one dares to remove
                 any jar any longer. The project classpath is a complete mess and if a classpath problem arises, hell on
-                earth invites you for a ride. In one of my former projects, I found some ldap related jar in the
-                classpath, whose sheer presence, as I found out after much research, accelerated LDAP access. So
+                earth invites you for a ride. In one of our former projects, we found some ldap related jar in the
+                classpath, whose sheer presence, as we found out after much research, accelerated LDAP access. So
                 removing this jar would not have led to any errors at compile or runtime.
             </para>
             <para>Gradle offers you different ways to express what are first level and what are transitive dependencies.
                 Gradle allows you for example to store your jars in CVS or SVN without XML descriptor files and still
-                use transitive dependency management. Gradle also validates your dependency hierarchy against the
-                reality of your code by using only the first level dependencies for compiling.
+                use transitive dependency management. Also, not all techniques for transitive dependency management deal with
+                the problem described above equally well.
             </para>
         </section>
         <section id='sub:version_conflicts'>
             <title>Version conflicts</title>
-            <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
+            <para>Conflicting versions of the same jar should be detected and either resolved or cause an exception. If you don't use
+                transitive dependency management, version conflicts are undetected and the mostly accidental fragile order of the classpath
+                will determine, what version of a dependency will win. For example adding a dependency with a particular version to a subproject
+                might change that order and then will led to all kind of surprising side effects. You might also want to learn where conflicting
+                versions are used as you might want to consolidate on a particular version of an dependency across your organization. With a good conflict
+                reporting that information can be used to communicate with the teams to solve this.
+            </para>
+            <para>It is common that different dependencies rely on different versions of
+                another dependency which leads to a version conflictm as 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'/>).</para>
             <para>Gradle offers following conflict resolution strategies:
                 <itemizedlist>
@@ -113,6 +119,8 @@
                         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>
+                    <listitem>We are working on making conflict resolution fully customizable.
+                    </listitem>
                 </itemizedlist>
                 Gradle provides means to resolve version conflicts:
                 <itemizedlist>
@@ -167,17 +175,9 @@
                 particular jar. Both also use repositories where the actual jars are placed together with their
                 descriptor files. And both offer resolution for conflicting jar versions in one form or the other. Yet
                 we think the differences of both approaches are significant
-                in terms of flexibility and maintainability. Beside this, Ivy fully supports the Maven dependency
-                handling. So with Ivy you have access to both worlds. We like Ivy very much. Gradle uses it under the
-                hood for its dependency management. Ivy is most often used via Ant and XML descriptors. But it also has
-                an API. We integrate deeply with Ivy via its API. This enables us to build new concepts on top
-                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, 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.
+                in terms of flexibility and maintainability. Originally Gradle did use Ivy under the
+                hood for its dependency management. This has been replaced with a native Gradle dependency resolution engine. This resolution engine
+                supports both pom and ivy descriptor files.
             </para>
         </section>
     </section>
@@ -307,7 +307,7 @@
                     downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors,  you want to download only the artifact jar, without
                     the dependencies.
                     <footnote>
-                        <para>Gradle supports partial multiproject builds (see<xref linkend='multi_project_builds'/>).
+                        <para>Gradle supports partial multiproject builds (see <xref linkend='multi_project_builds'/>).
                         </para>
                     </footnote>
                     And sometimes you want to download a zip from a repository, that does not have module descriptors.
@@ -510,6 +510,19 @@
             <para>You can generate dependency reports from the command line (see <xref linkend="para:commandline_dependency_report"/>).
                 With the help of the Project report plugin (see <xref linkend="project_reports_plugin"/>) such a report can be created by your build.
             </para>
+            <para>
+                Since Gradle 1.2 there is also a new programmatic API to access the resolved dependency information.
+                The dependency reports (see the previous paragraph) are using this API behind the hood.
+                The API lets you to walk the resolved dependency graph and provides information about the dependencies.
+                With the coming releases the API will grow to provide more information about the resolution result.
+                For more information about the API please refer to the javadocs on
+                <apilink class="org.gradle.api.artifacts.ResolvableDependencies" method="getResolutionResult"/>.
+                Potential usages of the <apilink class="org.gradle.api.artifacts.result.ResolutionResult"/> API:
+                <itemizedlist>
+                    <listitem>Creation of advanced dependency reports tailored to your use case.</listitem>
+                    <listitem>Enabling the build logic to make decisions based on the content of the dependency graph.</listitem>
+                </itemizedlist>
+            </para>
         </section>
     </section>
     <section id='sec:working_with_dependencies'>
@@ -705,6 +718,12 @@
                 <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with custom patterns">
                     <sourcefile file="build.gradle" snippet="ivy-repo-with-custom-pattern"/>
                 </sample>
+                <para>
+                    Each <literal>ivyPattern</literal> or <literal>artifactPattern</literal> specified for a repository adds an <emphasis>
+                    additional</emphasis> pattern, on top of any url/layout based patterns defined.
+                    Values supplied as <literal>ivyPattern</literal> or <literal>artifactPattern</literal> should be fully qualified URLs as they are not resolved
+                    relative to the <literal>url</literal> parameter for the repository. Any unqualified patterns will be resolved as a file path, relative to the project base directory.
+                </para>
             </section>
             <section>
                 <title>Accessing password protected Ivy repositories</title>
@@ -779,38 +798,38 @@ someroot/[artifact]-[revision].[ext]
         <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>
+                    <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>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>
+                        <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>
-                </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>
+                </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>
diff --git a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
index cc18efb..3354d6f 100644
--- a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
+++ b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
@@ -44,7 +44,7 @@
             <td><link linkend="groovy_plugin">Groovy</link></td><td>Adds Groovy configuration to <filename>.project</filename> file.</td>
         </tr>
         <tr>
-                <td><link linkend="scala_plugin">Scala</link></td><td>Adds Scala support to <filename>.project</filename> file.</td>
+            <td><link linkend="scala_plugin">Scala</link></td><td>Adds Scala support to <filename>.project</filename> and <filename>.classpath</filename> files.</td>
         </tr>
         <tr>
             <td><link linkend="war_plugin">War</link></td><td>Adds web application support to <filename>.project</filename> file.
diff --git a/subprojects/docs/src/docs/userguide/embedding.xml b/subprojects/docs/src/docs/userguide/embedding.xml
index 14a6b73..32f6591 100644
--- a/subprojects/docs/src/docs/userguide/embedding.xml
+++ b/subprojects/docs/src/docs/userguide/embedding.xml
@@ -16,7 +16,7 @@
 <chapter id="embedding">
     <title>Embedding Gradle</title>
 
-    <section id='sec:Introduction'>
+    <section id='sec:embedding_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,
@@ -77,7 +77,7 @@
         </para>
     </section>
 
-    <section id='sec:Daemon'>
+    <section id='sec:embedding_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.
@@ -87,7 +87,7 @@
         </para>
     </section>
 
-    <section id='sec:Quickstart'>
+    <section id='sec:embedding_quickstart'>
         <title>Quickstart</title>
         <para>Since the tooling API is an interface for a programmer most of the documentation lives in the Javadoc.
             This is exactly our intention - we don't expect this chapter to grow very much.
diff --git a/subprojects/docs/src/docs/userguide/featureLifecycle.xml b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
new file mode 100644
index 0000000..6ae9411
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
@@ -0,0 +1,132 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="feature_lifecycle">
+    <title>The Feature Lifecycle</title>
+    <para>
+        Gradle is under constant development and improvement. New versions are also delivered on a regular and frequent basis
+        (approximately every 6 weeks). Continuous improvement combined with frequent delivery allows new features to be made available
+        to users early and for invaluable real world feedback to be incorporated into the development process. Getting
+        new functionality into the hands of users regularly is a core value of the Gradle platform.
+        At the same time, API and feature stability is taken very seriously and is also considered a core value of the Gradle platform.
+        This is something that is engineered into the development process by design choices and automated testing, and is formalised by
+        the <xref linkend='backwards_compatibility'>Backwards Compatibility Policy</xref>.
+    </para>
+    <para>
+        The Gradle
+        <firstterm>feature lifecycle</firstterm>
+        has been designed to meet these goals. It also serves
+        to clearly communicate to users of Gradle what the state of a feature is. The term
+        <firstterm>feature</firstterm>
+        typically means
+        an API or DSL method or property in this context, but it is not restricted to this definition.
+        Command line arguments and modes of execution (e.g. the Build Daemon) are two examples of other kinds of features.
+    </para>
+    <section>
+        <title>States</title>
+        <para>
+            Features can be in one of 4 states:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>Internal</para>
+            </listitem>
+            <listitem>
+                <para>Incubating</para>
+            </listitem>
+            <listitem>
+                <para>Public</para>
+            </listitem>
+            <listitem>
+                <para>Deprecated</para>
+            </listitem>
+        </itemizedlist>
+        <section>
+            <title>Internal</title>
+            <para>
+                Internal features are not designed for public use and are only intended to be used by Gradle itself. They can change in any way at any
+                point in time without any notice. Therefore, we recommend avoiding the use of such features.
+                Internal features are not documented. If it appears in this User Guide, the DSL Reference or the API Reference documentation then
+                the feature is not internal.
+            </para>
+            <para>
+                Internal features may evolve into public features.
+            </para>
+        </section>
+        <section>
+            <title>Incubating</title>
+            <para>
+                When new features are introduced to Gradle, they are usually introduced in an <firstterm>incubating</firstterm> state.
+                A feature in this state is intended to be used by Gradle users, but may change in future Gradle versions until it is no longer incubating.
+                Changes to incubating features for a Gradle release will be highlighted in the release notes for that release.
+                The incubation period for new features varies depending on the scope, complexity and nature of the feature.
+            </para>
+            <para>
+                Features are introduced in the incubating state to allow real world feedback to be incorporated into the feature before it is made public and
+                locked down to provide backwards compatibility.
+                It also gives users who are willing to accept potential future changes early access to the feature so they can put it into use immediately.
+            </para>
+            <para>
+                Features in incubation are clearly indicated to be so. In the source code, all methods/properties/classes that are incubating are
+                annotated with <apilink class="org.gradle.api.Incubating"/>, which is also used to specially mark them in the DSL and API references. If an incubating
+                feature is discussed in this User Guide, it will be explicitly said to be in the incubating state.
+            </para>
+        </section>
+        <section>
+            <title>Public</title>
+            <para>
+                The default state for a non-internal feature is <firstterm>public</firstterm>.
+                Anything that is documented in the User Guide, DSL Reference or API references that
+                is not explicitly said to be incubating or deprecated is considered public. Features are said to be <firstterm>promoted</firstterm> from an
+                incubating state to public. The release notes for each release indicate which previously incubating features are being promoted by the release.
+            </para>
+            <para>
+                A public feature will <emphasis>never</emphasis> be removed or intentionally changed without undergoing deprecation.
+                All public features are subject to the backwards compatibility policy.
+            </para>
+        </section>
+        <section>
+            <title>Deprecated</title>
+            <para>
+                Some features will become superseded or irrelevant due to the natural evolution of Gradle. Such features will eventually be removed
+                from Gradle after being <firstterm>deprecated</firstterm>. A deprecated feature will <emphasis>never</emphasis>
+                be changed, until it is finally removed according to the backwards compatibility policy.
+            </para>
+            <para>
+                Deprecated features are clearly indicated to be so. In the source code, all methods/properties/classes that are deprecated are
+                annotated with
+                <literal>@java.lang.Deprecated</literal>
+                which is reflected in the DSL and API references.
+                In most cases, there is a replacement for the deprecated element, and this will be described in the documentation.
+                Using a deprecated feature will also result in runtime warning in Gradle's output.
+            </para>
+            <para>
+                Use of deprecated features should be avoided. The release notes for each release indicate any features that are being deprecated
+                by the release.
+            </para>
+        </section>
+    </section>
+    <section id="backwards_compatibility">
+        <title>Backwards Compatibility Policy</title>
+        <para>
+            Gradle provides backwards compatibility for across major versions (e.g. <literal>1.x</literal>, <literal>2.x</literal>
+            etc.).
+            Once a public feature is introduced or promoted in a Gradle release it will remain indefinitely or until it is deprecated. Once
+            deprecated, it may be removed in the next major release. Deprecated features may be supported across major
+            releases, but this is not guaranteed.
+        </para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/gradleDaemon.xml b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
index f3090f9..48306ba 100644
--- a/subprojects/docs/src/docs/userguide/gradleDaemon.xml
+++ b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
@@ -29,7 +29,7 @@
             <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>
+                <listitem>When discovering what a build can do, where <userinput>gradle tasks</userinput> 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>
@@ -93,7 +93,7 @@
                 <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:
+                    This also has 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>
@@ -110,9 +110,6 @@
             (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
@@ -128,10 +125,9 @@
     </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"/>
+        <title>Configuring the daemon</title>
+        <para>Some daemon settings, such as JVM arguments, memory settings or the Java home, can be configured.
+            Please find more information in <xref linkend="sec:gradle_configuration_properties"/>
         </para>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index d92a73a..8720df0 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -69,7 +69,7 @@
                 <td>All tasks which produce the compile classpath. This includes the <literal>jar</literal> task for
                     project dependencies included in the <literal>compile</literal> configuration.
                 </td>
-                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td><apilink class="org.gradle.api.tasks.compile.JavaCompile"/></td>
                 <td>Compiles production Java source files using javac.</td>
             </tr>
             <tr>
@@ -98,7 +98,7 @@
                 <td>
                     <literal>compile</literal>, plus all tasks which produce the test compile classpath.
                 </td>
-                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td><apilink class="org.gradle.api.tasks.compile.JavaCompile"/></td>
                 <td>Compiles test Java source files using javac.</td>
             </tr>
             <tr>
@@ -199,7 +199,7 @@
                 <td>
                     All tasks which produce the source set's compile classpath.
                 </td>
-                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td><apilink class="org.gradle.api.tasks.compile.JavaCompile"/></td>
                 <td>Compiles the given source set's Java source files using javac.</td>
             </tr>
             <tr>
@@ -1057,7 +1057,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
+        <para>The Java plugin adds a <apilink class="org.gradle.api.tasks.compile.JavaCompile"/> instance for each
             source set in the project. Some of the most common configuration options are shown below.
         </para>
         <table>
diff --git a/subprojects/docs/src/docs/userguide/logging.xml b/subprojects/docs/src/docs/userguide/logging.xml
index a5ece50..d929950 100644
--- a/subprojects/docs/src/docs/userguide/logging.xml
+++ b/subprojects/docs/src/docs/userguide/logging.xml
@@ -74,23 +74,22 @@
             </tr>
             <tr>
                 <td>
-                    <literal>-q</literal>
+                    <literal>-q</literal> or <literal>--quiet</literal>
                 </td>
                 <td>QUIET and higher</td>
             </tr>
             <tr>
                 <td>
-                    <literal>-i</literal>
+                    <literal>-i</literal> or <literal>--info</literal>
                 </td>
                 <td>INFO and higher</td>
             </tr>
             <tr>
                 <td>
-                    <literal>-d</literal>
+                    <literal>-d</literal> or <literal>--debug</literal>
                 </td>
                 <td>DEBUG and higher (that is, all log messages)</td>
             </tr>
-
         </table>
         <table id='stacktraces'>
             <title>Stacktrace command-line options</title>
@@ -103,13 +102,13 @@
             <tr>
                 <td>No stacktrace options</td>
                 <td>No stacktraces are printed to the console in case of a build error (e.g. a compile error). Only in
-                    case of internal exceptions will stacktraces be printed. If the loglevel option <literal>-d</literal>
+                    case of internal exceptions will stacktraces be printed. If the <literal>DEBUG</literal> log level
                     is chosen, truncated stacktraces are always printed.
                 </td>
             </tr>
             <tr>
                 <td>
-                    <literal>-s</literal>
+                    <literal>-s</literal> or <literal>--stacktrace</literal>
                 </td>
                 <td>Truncated stacktraces are printed. We recommend this over full stacktraces. Groovy full stacktraces
                     are extremely verbose (Due to the underlying dynamic invocation mechanisms. Yet they usually do not
@@ -118,7 +117,7 @@
             </tr>
             <tr>
                 <td>
-                    <literal>-S</literal>
+                    <literal>-S</literal> or <literal>--full-stacktrace</literal>
                 </td>
                 <td>The full stacktraces are printed out.</td>
             </tr>
diff --git a/subprojects/docs/src/docs/userguide/multiproject.xml b/subprojects/docs/src/docs/userguide/multiproject.xml
index 0d09e09..38d62e5 100644
--- a/subprojects/docs/src/docs/userguide/multiproject.xml
+++ b/subprojects/docs/src/docs/userguide/multiproject.xml
@@ -550,6 +550,29 @@
             </para>
         </section>
     </section>
+    <section id="sec:decoupled_projects">
+        <title>Decoupled Projects</title>
+        <para>Gradle allows any project to access any other project during both the configuration and execution phases. While this provides a great deal of power
+            and flexibility to the build author, it also limits the flexibility that Gradle has when building those projects. For instance, this tight <emphasis>coupling</emphasis> of projects
+            effectively prevents Gradle from building multiple projects in parallel, or from substituting a pre-built artifact in place of a project dependency.
+        </para>
+        <para>Two projects are said to be <emphasis>decoupled</emphasis> if they do not directly access each other's project model. Decoupled projects may only interact in terms of
+            declared dependencies: project dependencies (<xref linkend='sub:project_dependencies'/>) and/or task dependencies (<xref linkend='sec:task_dependencies'/>).
+            Any other form of project interaction (ie. by modifying another project object or by reading a value from another project object) causes the projects to
+            be coupled.
+        </para>
+        <para>
+            A very common way for projects to be coupled is by using configuration injection (<xref linkend='sec:cross_project_configuration'/>). It may not be immediately apparent, but using key
+            Gradle features like the <literal>allprojects</literal> and <literal>subprojects</literal> keywords automatically cause your projects to be coupled. This is
+            because these keywords are used in a <literal>build.gradle</literal> file, which defines a project. Often this is a "root project" that does nothing more than
+            define common configuration, but as far as Gradle is concerned this root project is still a fully-fledged project, and by using <literal>allprojects</literal>
+            that project is effectively coupled to all other projects.
+        </para>
+        <para>This means that using any form of shared build script logic or configuration injection (<literal>allprojects</literal>, <literal>subprojects</literal>, etc)
+            will cause your projects to be coupled. As we extend the concept of project decoupling and provide features that take advantage of decoupled projects,
+            we will also introduce new features to help you to solve common use cases (like configuration injection) without causing your projects to be coupled.
+        </para>
+    </section>
     <section id="sec:multiproject_build_and_test">
         <title>Multi-Project Building and Testing</title>
         <para>The <literal>build</literal> task of the Java plugin is typically used to compile, test, and perform
diff --git a/subprojects/docs/src/docs/userguide/plugins.xml b/subprojects/docs/src/docs/userguide/plugins.xml
index 5a7a4f4..54f8777 100644
--- a/subprojects/docs/src/docs/userguide/plugins.xml
+++ b/subprojects/docs/src/docs/userguide/plugins.xml
@@ -18,7 +18,7 @@
     <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.
+        Plugins add new tasks (e.g. <apilink class='org.gradle.api.tasks.compile.JavaCompile'/>), 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>
diff --git a/subprojects/docs/src/docs/userguide/projectReports.xml b/subprojects/docs/src/docs/userguide/projectReports.xml
index 6096703..e32f66e 100644
--- a/subprojects/docs/src/docs/userguide/projectReports.xml
+++ b/subprojects/docs/src/docs/userguide/projectReports.xml
@@ -16,12 +16,6 @@
 <chapter id="project_reports_plugin" xmlns:xi="http://www.w3.org/2001/XInclude">
     <title>The Project Report Plugin</title>
 
-    <note>
-        <para>The Project report plugin is currently a work in progress, and at this stage doesn't do particularly
-            much. We plan to add much more to these reports in future releases of Gradle.
-        </para>
-    </note>
-
     <para>The Project report plugin adds some tasks to your project which generate reports containing useful
         information about your build. Those tasks generate exactly the same content as the command line reports triggered
         by <userinput>gradle tasks</userinput>, <userinput>gradle dependencies</userinput> and
@@ -29,6 +23,9 @@
         In contrast to the command line reports, the report plugin generates the reports into a file. There is also an
         aggregating task that depends on all report tasks added by the plugin.
     </para>
+    <para>
+        We plan to add much more to the existing reports and create additional ones in future releases of Gradle.
+    </para>
 
     <section>
         <title>Usage</title>
diff --git a/subprojects/docs/src/docs/userguide/publishingIvy.xml b/subprojects/docs/src/docs/userguide/publishingIvy.xml
new file mode 100644
index 0000000..4219bb5
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/publishingIvy.xml
@@ -0,0 +1,228 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="publishing_ivy">
+    <title>Ivy Publishing (new)</title>
+    <note>
+        <para>
+            This chapter describes the new <emphasis>incubating</emphasis> Ivy publishing support introduced in Gradle 1.3.
+            If you are looking for documentation on the “traditional” Ivy publishing support please see <xref linkend="artifact_management"/>.
+        </para>
+    </note>
+    <section>
+        <para>
+            This chapter describes how to publish build artifacts in the <ulink url="http://ant.apache.org/ivy/">Apache Ivy</ulink> format, usually to a repository
+            for consumption by other builds or projects. What is published is one or more artifacts created by the build, and an Ivy <firstterm>module descriptor</firstterm>
+            that describes the artifacts and the dependencies of the artifacts, if any.
+        </para>
+        <para>
+            A published Ivy module can be consumed by Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Ivy format.
+        </para>
+    </section>
+    <section>
+        <title>The “<literal>ivy-publish</literal>” Plugin</title>
+        <para>
+            The ability to publish in the Ivy format is provided by the “<literal>ivy-publish</literal>” plugin.
+        </para>
+        <sample id="publishing_ivy:apply_plugin" dir="ivypublish-new" title="Applying the “ivy-publish” plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin" />
+        </sample>
+        <para>
+            This plugin does the following:
+        </para>
+        <itemizedlist>
+            <listitem>Applies the “<literal>publishing</literal>” plugin</listitem>
+            <listitem>
+                Creates a publication in the <literal>publishing.publications</literal> container of type <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> named “<literal>ivy</literal>”
+                (see <xref linkend="publishing_ivy:publications"/>)
+            </listitem>
+            <listitem>
+                Establish a rule to automatically create an <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> task for each Ivy publishing repository added
+                (see <xref linkend="publishing_ivy:repositories"/>)
+            </listitem>
+        </itemizedlist>
+        <para>
+            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
+            This extension provides a container of named publications and a container of named repositories. The “<literal>ivy-publish</literal>” works with
+            <apilink class="org.gradle.api.publish.ivy.IvyPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository" /> repositories.
+        </para>
+    </section>
+    <section id="publishing_ivy:publications">
+        <title>Publications</title>
+        <note>
+            <para>
+                If you are not familiar with project artifacts and configurations, you should read the <xref linkend="artifact_management" />
+                that introduces these concepts. This chapter also describes “publishing artifacts” using a different mechanism than what is
+                described in this chapter. The publishing functionality described here will eventually supersede that functionality.
+            </para>
+        </note>
+        <para>
+            Publication objects describe the structure/configuration of a publication to be created. Publications are published to repositories via tasks, and the
+            configuration of the publication object determines exactly what is published. All of the publications of a project are defined in the
+            <apilink class="org.gradle.api.publish.PublishingExtension" method="getPublications()" /> container. Each publication has a unique name within the project.
+        </para>
+        <para>
+            At this time, it is not possible to create arbitrary publication objects. When the “<literal>ivy-publish</literal>” plugin is applied it creates a
+            single publication named “<literal>ivy</literal>”. This publication will publish all of artifacts of all of the project's visible configurations,
+            and any configurations that they extend from.
+        </para>
+        <sample dir="ivypublish-new" id="publishing_ivy:build_to_publish" title="A build to publish">
+            <sourcefile file="build.gradle" snippet="input" />
+        </sample>
+        <para>
+            The “<literal>publishing.publications.ivy</literal>” publication that was added to the “<literal>publishing.publications</literal>” container of the project
+            will be configured to publish two artifacts:
+        </para>
+        <itemizedlist>
+            <listitem>The primary “jar” artifact automatically created by the “<literal>java</literal>” plugin (see <xref linkend="java_plugin"/>)</listitem>
+            <listitem>The source “jar” artifact that has been explicitly configured in this build</listitem>
+        </itemizedlist>
+        <para>
+            When this publication is published, the <firstterm>module descriptor</firstterm> (i.e. the <literal>ivy.xml</literal> file) that is produced will look like…
+        </para>
+        <tip>
+            <para>Note that the <literal>«PUBLICATION-TIME-STAMP»</literal> in this example Ivy module descriptor will be the timestamp of when the descriptor was generated.</para>
+        </tip>
+        <sample dir="ivypublish-new" id="publishing_ivy:output_ivy.xml" title="Example generated ivy.xml">
+            <sourcefile file="output-ivy.xml" snippet="content" />
+        </sample>
+        <para>
+            The attributes of the <literal><info></literal> tag identify the module. These values are derived from the following project properties:
+        </para>
+        <itemizedlist>
+            <listitem><literal>organisation</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
+            <listitem><literal>module</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
+            <listitem><literal>revision</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
+            <listitem><literal>status</literal> - <apilink class="org.gradle.api.Project" method="getStatus()" /></listitem>
+        </itemizedlist>
+        <para>
+            Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+        </para>
+        <section>
+            <title>Modifying the published module descriptor</title>
+            <para>
+                Notice that the <literal>junit</literal> dependency that appears in the descriptor above is different to what was actually used in the project.
+                This is because of the descriptor modification that was declared.
+            </para>
+            <sample dir="ivypublish-new" id="publishing_ivy:descriptor_mod" title="Modifying the Ivy descriptor">
+                <sourcefile file="build.gradle" snippet="descriptor-mod" />
+            </sample>
+            <para>
+                It is possible to modify any aspect of the created descriptor should you need to.
+                This means that it is also possible to modify the descriptor in such a way that it is no longer a valid
+                Ivy module descriptor, so care must be taken when using this feature.
+            </para>
+            <para>
+                See <apilink class="org.gradle.api.publish.ivy.IvyModuleDescriptor" method="withXml(org.gradle.api.Action)" /> for the relevant API reference documentation on descriptor modification.
+            </para>
+        </section>
+    </section>
+    <section id="publishing_ivy:repositories">
+        <title>Repositories</title>
+        <para>
+            Publications are published to repositories. The repositories to publish to are defined by the <apilink class="org.gradle.api.publish.PublishingExtension" method="getRepositories()" />
+            container.
+        </para>
+        <sample dir="ivypublish-new" id="publishing_ivy:repositories" title="Declaring repositories to publish to">
+            <sourcefile file="build.gradle" snippet="repositories" />
+        </sample>
+        <para>
+            The DSL used to declare repositories to publish to is the same DSL that is used to declare repositories to consume dependencies from,
+            <apilink class="org.gradle.api.artifacts.dsl.RepositoryHandler" />. However, in the context of Ivy publication only the repositories created
+            by the <literal>ivy()</literal> methods can be used as publication destinations. You cannot publish an <literal>IvyPublication</literal> to
+            a Maven repository for example.
+        </para>
+    </section>
+    <section id="publishing_ivy:publishing">
+        <title>Performing a publish</title>
+        <para>
+            The “<literal>ivy-publish</literal>” plugin automatically creates a <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" />
+            task for each <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> and <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository" />
+            combination in the <literal>publishing.publications</literal> and <literal>publishing.repositories</literal> containers respectively.
+        </para>
+        <para>
+            In the example we have been working with so far, given that the publication that the “<literal>ivy-publish</literal>”
+            plugin creates is named “<literal>ivy</literal>” and that the default name for repositories created using the <literal>ivy()</literal> methods of the
+            <literal>publishing.repositories</literal> container is also “<literal>ivy</literal>”, a publish task will be created with the name
+            “<literal>publishIvyPublicationToIvyRepository</literal>”. The naming pattern is
+            “<literal>publish«<emphasis>NAME OF PUBLICATION</emphasis>»PublicationTo«<emphasis>NAME OF REPOSITORY</emphasis>»Repository</literal>”.
+        </para>
+        <para>
+            Executing this task will build all of the artifacts to be published, and transfer them to the repository.
+        </para>
+        <!--
+            We are cheating here. We can't use the tested output mechanism because we need to modify the sample before we can run it.
+            We could avoid this if we were able to grab the output from a test (org.gradle.api.publish.ivy.SamplesIvyPublishIntegrationTest)
+        -->
+        <para>Output of <userinput>gradle publishIvyPublicationToIvyRepository</userinput></para>
+        <screen>:subproject:compileJava
+:subproject:processResources UP-TO-DATE
+:subproject:classes
+:subproject:jar
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:sourceJar
+:publishIvyPublicationToIvyRepository
+
+BUILD SUCCESSFUL
+
+Total time: 1 sec</screen>
+    <section>
+        <title>The “<literal>publish</literal>” lifecycle task</title>
+        <para>
+            The “<literal>publish</literal>” plugin (that the “<literal>ivy-publish</literal>” plugin implicitly applies) adds a lifecycle task
+            that can be used to publish all publications to all applicable repositories named “<literal>publish</literal>”.
+        </para>
+        <para>
+            In more concrete terms, executing this task will execute all <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> tasks in the project.
+            This is usually the most convenient way to perform a publish.
+        </para>
+        <!--
+            Cheating again, see above.
+        -->
+        <para>Output of <userinput>gradle publish</userinput></para>
+        <screen>:subproject:compileJava
+:subproject:processResources UP-TO-DATE
+:subproject:classes
+:subproject:jar
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:sourceJar
+:publishIvyPublicationToIvyRepository
+:publish
+
+BUILD SUCCESSFUL
+
+Total time: 1 sec</screen>
+        </section>
+    </section>
+    <section>
+        <title>Future features</title>
+        <para>
+            The “<literal>ivy-publish</literal>” functionality as described above is incomplete, as the feature is still <firstterm>incubating</firstterm>.
+            Over the coming Gradle releases, the functionality will be expanded to include (but not limited to):
+        </para>
+        <itemizedlist>
+            <listitem>Convenient customisation of module attributes (<literal>module</literal>, <literal>organisation</literal> etc.)</listitem>
+            <listitem>Fine grained control of which artifacts are published</listitem>
+            <listitem>Multiple discreet publications per project</listitem>
+        </itemizedlist>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/scalaPlugin.xml b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
index a4dddf1..410d040 100644
--- a/subprojects/docs/src/docs/userguide/scalaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
@@ -156,7 +156,7 @@
     </section>
 
     <section>
-        <title>Dependency Management</title>
+        <title>Dependency management</title>
         <para>The Scala plugin adds a <literal>scalaTools</literal> configuration, which it uses to locate the Scala
             tools, such as scalac, to use. You must specify the version of Scala to use. Below is an example.
         </para>
@@ -166,7 +166,7 @@
     </section>
 
     <section>
-        <title>Convention Properties</title>
+        <title>Convention properties</title>
         <para>The Scala plugin does not add any convention properties to the project.</para>
     </section>
     
@@ -272,4 +272,77 @@
         </para>
     </section>
 
+    <section>
+        <title>Compiling in external process</title>
+        <para>
+            When <literal>scalaCompileOptions.fork</literal> is set to <literal>true</literal>, compilation will take place
+            in an external process. The details of forking depend on which compiler is used. The Ant based compiler
+            (<literal>scalaCompileOptions.useAnt = true</literal>) will fork a new process for every <literal>ScalaCompile</literal> task,
+            and does not fork by default. The Zinc based compiler (<literal>scalaCompileOptions.useAnt = false</literal>) will leverage
+            the Gradle compiler daemon, and does so by default.
+        </para>
+        <para>Memory settings for the external process default to the JVM's defaults. To adjust memory settings,
+            configure <literal>scalaCompileOptions.forkOptions</literal> as needed:
+            <sample id="zinc" dir="scala/zinc" title="Adjusting memory settings">
+                <sourcefile file="build.gradle" snippet="adjust-memory"/>
+            </sample>
+        </para>
+    </section>
+
+    <section>
+        <title>Incremental compilation</title>
+        <para>
+            By compiling only classes whose source code has changed since the previous compilation, and classes affected by these changes,
+            incremental compilation can significantly reduce Scala compilation time. It is particularly effective when frequently compiling
+            small code increments, as is often done at development time.
+        </para>
+
+        <para>
+            The Scala plugin now supports incremental compilation by integrating with <ulink url="https://github.com/typesafehub/zinc">Zinc</ulink>,
+            a standalone version of <ulink url="https://github.com/harrah/xsbt">sbt</ulink>'s incremental Scala compiler. To switch the
+            <literal>ScalaCompile</literal> task from the default Ant based compiler to the new Zinc based compiler, set
+            <literal>scalaCompileOptions.useAnt</literal> to <literal>false</literal>:
+            <sample id="zinc" dir="scala/zinc" title="Activating the Zinc based compiler">
+                <sourcefile file="build.gradle" snippet="use-zinc"/>
+            </sample>
+        </para>
+
+        <para>
+            Except where noted in the<ulink url="http://gradle.org/docs/current/dsl/org.gradle.api.tasks.scala.ScalaCompile.html">API documentation</ulink>,
+            the Zinc based compiler supports exactly the same configuration options as the Ant based compiler. Note, however, that the Zinc compiler requires
+            Java 6 or higher to run. This means that Gradle itself has to be run with Java 6 or higher.
+        </para>
+
+        <para>
+            The Scala plugin adds a configuration named <literal>zinc</literal> to resolve the Zinc library and its dependencies. To override the
+            Zinc version that Gradle uses by default, add an explicit Zinc dependency (for example <literal>zinc "com.typesafe.zinc:zinc:0.1.4"</literal>).
+            Regardless of which Zinc version is used, Zinc will always use the Scala compiler found on the <literal>scalaTools</literal> configuration.
+        </para>
+
+        <para>
+            Just like Gradle's Ant based compiler, the Zinc based compiler supports joint compilation of Java and Scala code. By default, all Java and Scala code
+            under <literal>src/main/scala</literal> will participate in joint compilation. With the Zinc based compiler, even Java code will be compiled incrementally.
+        </para>
+
+        <para>
+            Incremental compilation requires dependency analysis of the source code. The results of this analysis are stored in the file designated
+            by <literal>scalaCompileOptions.incrementalOptions.analysisFile</literal> (which has a sensible default). In a multi-project build, analysis
+            files are passed on to downstream <literal>ScalaCompile</literal> tasks to enable incremental compilation across project boundaries. For
+            <literal>ScalaCompile</literal> tasks added by the Scala plugin, no configuration is necessary to make this work. For other
+            <literal>ScalaCompile</literal> tasks, <literal>scalaCompileOptions.incrementalOptions.publishedCode</literal> needs to be configured to point
+            to the classes folder or Jar archive by which the code is passed on to compile class paths of downstream <literal>ScalaCompile</literal> tasks.
+            Note that if <literal>publishedCode</literal> is not set correctly, downstream tasks may not recompile code affected by upstream changes,
+            leading to incorrect compilation results.
+        </para>
+
+        <para>
+            Due to the overhead of dependency analysis, a clean compilation or a compilation after a larger code change may take longer than with the Ant based compiler.
+            For CI builds and release builds, we currently recommend to use the Ant based compiler.
+        </para>
+
+        <para>
+            Note that Zinc's Nailgun based daemon mode is not supported. Instead, we plan to enhance Gradle's own compiler daemon to stay alive across Gradle
+            invocations, reusing the same Scala compiler. This is expected to yield another significant speedup for Scala compilation.
+        </para>
+    </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 096e25b..46b4bb6 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -75,8 +75,8 @@
         </table>
     </section>
     <section>
-        <title>Experimental language plugins</title>
-        <para>These experimental plugins add support for various languages:</para>
+        <title>Incubating language plugins</title>
+        <para>These plugins add support for various languages:</para>
         <table>
             <title>Language plugins</title>
             <thead>
@@ -213,6 +213,17 @@
                     </para>
                 </td>
             </tr>
+            <tr>
+                <td>
+                    <link linkend="bootstrap_plugin"><literal>maven2Gradle</literal></link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds experimental support for converting an existing maven build into a Gradle project.
+                    </para>
+                </td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/userguide/troubleshooting.xml b/subprojects/docs/src/docs/userguide/troubleshooting.xml
index dc44756..9a2be0a 100644
--- a/subprojects/docs/src/docs/userguide/troubleshooting.xml
+++ b/subprojects/docs/src/docs/userguide/troubleshooting.xml
@@ -37,7 +37,6 @@
         </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>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index 0ca2bce..4e910c9 100644
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -16,7 +16,6 @@
 <book xmlns:xi="http://www.w3.org/2001/XInclude">
     <bookinfo>
         <title>Gradle User Guide</title>
-        <subtitle>A better way to build</subtitle>
         <copyright>
             <year>2007-2012</year>
             <holder>Hans Dockter, Adam Murdoch</holder>
@@ -27,14 +26,6 @@
                 Notice, whether distributed in print or electronically.
             </para>
         </legalnotice>
-        <author>
-            <firstname>Hans</firstname>
-            <surname>Dockter</surname>
-        </author>
-        <author>
-            <firstname>Adam</firstname>
-            <surname>Murdoch</surname>
-        </author>
     </bookinfo>
     <xi:include href='introduction.xml'/>
     <xi:include href='overview.xml'/>
@@ -78,6 +69,7 @@
     <xi:include href='announcePlugin.xml'/>
     <xi:include href='buildAnnouncementsPlugin.xml'/>
     <xi:include href='applicationPlugin.xml'/>
+    <xi:include href='bootstrapPlugin.xml'/>
 	<xi:include href='depMngmt.xml'/>
     <xi:include href='artifactMngmt.xml'/>
     <xi:include href='mavenPlugin.xml'/>
@@ -91,9 +83,12 @@
     <xi:include href='initscripts.xml'/>
     <xi:include href='gradleWrapper.xml'/>
     <xi:include href='embedding.xml'/>
+    <xi:include href='comparingBuilds.xml'/>
+    <xi:include href='publishingIvy.xml'/>
     <!-- this is generated -->
     <xi:include href='../../../build/src/samplesList.xml'/>
     <xi:include href='potentialTraps.xml'/>
+    <xi:include href='featureLifecycle.xml'/>
     <xi:include href='commandLine.xml'/>
     <xi:include href='ideSupport.xml'/>
     <xi:include href='glossary.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index 1c289ee..10d6b23 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -166,7 +166,7 @@
     <section id="sec:specifying_multiple_files">
         <title>Specifying a set of input files</title>
         <para>Many objects in Gradle have properties which accept a set of input files. For example, the
-            <apilink class="org.gradle.api.tasks.compile.Compile"/> task has a <literal>source</literal> property,
+            <apilink class="org.gradle.api.tasks.compile.JavaCompile"/> task has a <literal>source</literal> property,
             which defines the source files to compile. You can set the value of this property using any of the types
             supported by the <link linkend="sec:file_collections">files()</link> method, which we have seen in above.
             This means you can set the property using, for example, a <classname>File</classname>, <classname>String</classname>,
diff --git a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
index eb9ff22..027f3ae 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.7.10'
+    compile 'org.codehaus.groovy:groovy-all:1.8.8'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
index 7d7e909..864b80f 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.8.4'
+    groovy 'org.codehaus.groovy:groovy-all:1.8.8'
     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 e1a64a8..a1ddd3f 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_8_4() {
-    assertEquals("1.8.4", groovycVersion)
+  void versionShouldBe1_8_8() {
+    assertEquals("1.8.8", groovycVersion)
   }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/build.gradle b/subprojects/docs/src/samples/ivypublish-new/build.gradle
new file mode 100644
index 0000000..8756493
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/build.gradle
@@ -0,0 +1,60 @@
+// START SNIPPET input
+apply plugin: 'java'
+// START SNIPPET use-plugin
+apply plugin: 'ivy-publish'
+// END SNIPPET use-plugin
+
+version = '1.0'
+group = 'org.gradle.test'
+
+dependencies {
+   compile 'junit:junit:4.8.2', project(':subproject')
+}
+
+repositories {
+    mavenCentral()
+}
+
+task sourceJar(type: Jar) {
+    baseName = 'ivypublishSource'
+    from sourceSets.main.java
+    classifier = 'src'
+}
+
+artifacts {
+    archives sourceJar
+}
+
+// END SNIPPET input
+// START SNIPPET repositories
+// START SNIPPET input
+// START SNIPPET descriptor-mod
+publishing {
+// END SNIPPET descriptor-mod
+// END SNIPPET input
+    repositories {
+        ivy {
+            url "http://mycompany.com/repo" // change to point to your repo
+            credentials {
+                username "user1"
+                password "secret"
+            }
+        }
+    }
+// END SNIPPET repositories
+// START SNIPPET input
+// START SNIPPET descriptor-mod
+    publications {
+        ivy {
+            descriptor {
+                withXml {
+                    asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
+                }
+            }
+        }
+    }
+// START SNIPPET repositories
+}
+// END SNIPPET repositories
+// END SNIPPET descriptor-mod
+// END SNIPPET input
diff --git a/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml b/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
new file mode 100644
index 0000000..2e395c1
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
@@ -0,0 +1,23 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<!-- START SNIPPET content -->
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+  <info organisation="org.gradle.test" module="ivypublish" revision="1.0" status="integration" publication="«PUBLICATION-TIME-STAMP»"/>
+  <configurations>
+    <conf name="archives" visibility="public" description="Configuration for archive artifacts."/>
+    <conf name="compile" visibility="private" description="Classpath for compiling the main sources."/>
+    <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
+    <conf name="runtime" visibility="private" description="Classpath for running the compiled main classes." extends="compile"/>
+    <conf name="testCompile" visibility="private" description="Classpath for compiling the test sources." extends="compile"/>
+    <conf name="testRuntime" visibility="private" description="Classpath for running the compiled test classes." extends="runtime,testCompile"/>
+  </configurations>
+  <publications>
+    <artifact name="ivypublish" type="jar" ext="jar" conf="archives,runtime"/>
+    <artifact name="ivypublishSource" type="jar" ext="jar" conf="archives" m:classifier="src" xmlns:m="http://ant.apache.org/ivy/maven"/>
+  </publications>
+  <dependencies>
+    <dependency org="junit" name="junit" rev="4.10" conf="compile->default"/>
+    <dependency org="ivypublish" name="subproject" rev="unspecified" conf="compile->default"/>
+  </dependencies>
+</ivy-module>
+<!-- END SNIPPET content -->
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/settings.gradle b/subprojects/docs/src/samples/ivypublish-new/settings.gradle
new file mode 100644
index 0000000..af72e93
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+rootProject.name = 'ivypublish'
+include 'subproject'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java b/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java
new file mode 100644
index 0000000..4360a7e
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public class SomeClass {
+}
diff --git a/subprojects/docs/src/samples/ivypublish-new/subproject/build.gradle b/subprojects/docs/src/samples/ivypublish-new/subproject/build.gradle
new file mode 100644
index 0000000..d2c87f8
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/subproject/build.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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: 'java'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/subproject/src/main/java/org/gradle/shared/Person.java b/subprojects/docs/src/samples/ivypublish-new/subproject/src/main/java/org/gradle/shared/Person.java
new file mode 100644
index 0000000..c4f58e6
--- /dev/null
+++ b/subprojects/docs/src/samples/ivypublish-new/subproject/src/main/java/org/gradle/shared/Person.java
@@ -0,0 +1,5 @@
+package org.gradle.shared;
+
+public class Person {
+    private String name;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/scala/customizedLayout/build.gradle b/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
index f62c322..9d1b2ce 100644
--- a/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
+++ b/subprojects/docs/src/samples/scala/customizedLayout/build.gradle
@@ -5,11 +5,9 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
-
-    compile 'org.scala-lang:scala-library:2.8.1'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
+    compile 'org.scala-lang:scala-library:2.9.1'
+    testCompile 'junit:junit:4.8.2'
 }
 
 // START SNIPPET define-main
diff --git a/subprojects/docs/src/samples/scala/fsc/build.gradle b/subprojects/docs/src/samples/scala/fsc/build.gradle
index 2545187..7f117f0 100644
--- a/subprojects/docs/src/samples/scala/fsc/build.gradle
+++ b/subprojects/docs/src/samples/scala/fsc/build.gradle
@@ -5,17 +5,16 @@ repositories {
 }
 
 dependencies {
-    // Libraries needed to run the scala tools
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
+    // Scala compiler and related tools
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
 
-    // Libraries needed for scala api
-    compile 'org.scala-lang:scala-library:2.8.1'
+    // Scala standard library
+    compile 'org.scala-lang:scala-library:2.9.1'
 }
 
 dependencies {
-    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    compile 'commons-collections:commons-collections:3.2'
+    testCompile 'junit:junit:4.8.2'
 }
 
 // START SNIPPET use-fsc
diff --git a/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle b/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
index da2a4c3..a686380 100644
--- a/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
+++ b/subprojects/docs/src/samples/scala/mixedJavaAndScala/build.gradle
@@ -7,9 +7,7 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
-
-    compile 'org.scala-lang:scala-library:2.8.1'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
+    compile 'org.scala-lang:scala-library:2.9.1'
+    testCompile 'junit:junit:4.8.2'
 }
diff --git a/subprojects/docs/src/samples/scala/quickstart/build.gradle b/subprojects/docs/src/samples/scala/quickstart/build.gradle
index 6f82628..d2f8e1e 100644
--- a/subprojects/docs/src/samples/scala/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/scala/quickstart/build.gradle
@@ -9,16 +9,15 @@ repositories {
 }
 
 dependencies {
-    // Libraries needed to run the scala tools
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
+    // Scala compiler and related tools
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
 
-    // Libraries needed for scala api
-    compile 'org.scala-lang:scala-library:2.8.1'
+    // Scala standard library
+    compile 'org.scala-lang:scala-library:2.9.1'
 }
 // END SNIPPET declare-scala-version
 
 dependencies {
-    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    compile 'commons-collections:commons-collections:3.2'
+    testCompile 'junit:junit:4.8.2'
 }
diff --git a/subprojects/docs/src/samples/scala/zinc/build.gradle b/subprojects/docs/src/samples/scala/zinc/build.gradle
new file mode 100644
index 0000000..05257d5
--- /dev/null
+++ b/subprojects/docs/src/samples/scala/zinc/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'scala'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    // Scala compiler and related tools
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
+
+    // Scala standard library
+    compile 'org.scala-lang:scala-library:2.9.1'
+}
+
+dependencies {
+    compile 'commons-collections:commons-collections:3.2'
+    testCompile 'junit:junit:4.8.2'
+}
+
+// START SNIPPET use-zinc
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.useAnt = false
+}
+// END SNIPPET use-zinc
+
+// START SNIPPET adjust-memory
+tasks.withType(ScalaCompile) {
+    configure(scalaCompileOptions.forkOptions) {
+        memoryMaximumSize = '1g'
+        jvmArgs = ['-XX:MaxPermSize=512m']
+    }
+}
+// END SNIPPET adjust-memory
diff --git a/subprojects/docs/src/samples/scala/zinc/readme.xml b/subprojects/docs/src/samples/scala/zinc/readme.xml
new file mode 100644
index 0000000..f39990f
--- /dev/null
+++ b/subprojects/docs/src/samples/scala/zinc/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Scala project using the Zinc based Scala compiler.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/api/Person.scala b/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/api/Person.scala
new file mode 100644
index 0000000..5effb67
--- /dev/null
+++ b/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/api/Person.scala
@@ -0,0 +1,9 @@
+package org.gradle.sample.api
+
+/**
+ * Defines the interface for a person.
+ */
+abstract trait Person
+{
+  def names: List[String]
+}
diff --git a/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/impl/PersonImpl.scala b/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
new file mode 100644
index 0000000..c6743e9
--- /dev/null
+++ b/subprojects/docs/src/samples/scala/zinc/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
@@ -0,0 +1,12 @@
+package org.gradle.sample.impl
+
+import org.gradle.sample.api.Person
+import org.apache.commons.collections.list.GrowthList;
+
+/**
+ * Immutable implementation of {@link Person}.
+ */
+class PersonImpl(val names: List[String]) extends Person
+{
+  private val importedList = new GrowthList();
+}
diff --git a/subprojects/docs/src/samples/toolingApi/build/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/build/src/main/java/org/gradle/sample/Main.java
deleted file mode 100644
index d58e265..0000000
--- a/subprojects/docs/src/samples/toolingApi/build/src/main/java/org/gradle/sample/Main.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.gradle.sample;
-
-import org.gradle.tooling.BuildLauncher;
-import org.gradle.tooling.GradleConnector;
-import org.gradle.tooling.ProjectConnection;
-
-import java.io.ByteArrayOutputStream;
-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 {
-            // Configure the build
-            BuildLauncher launcher = connection.newBuild();
-            launcher.forTasks("help");
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            launcher.setStandardOutput(outputStream);
-            launcher.setStandardError(outputStream);
-
-            // Run the build
-            launcher.run();
-        } finally {
-            // Clean up
-            connection.close();
-        }
-    }
-}
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
index 053888e..78911c0 100644
--- a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
@@ -16,4 +16,4 @@ dependencies {
     runtime 'org.slf4j:slf4j-simple:1.6.6'
 }
 
-mainClassName = 'org.gradle.sample.Main'
+mainClassName = 'org.gradle.sample.Main'
\ 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
index f9ccaa5..40e8d48 100644
--- 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
@@ -5,12 +5,22 @@ import org.gradle.tooling.model.*;
 import org.gradle.tooling.model.eclipse.*;
 
 import java.io.File;
+import java.lang.String;
+import java.lang.System;
 
 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]));
+            if (args.length > 1) {
+                connector.useGradleUserHomeDir(new File(args[1]));
+            }
+        }
+
         if (args.length > 0) {
             connector.useInstallation(new File(args[0]));
         }
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
index ef182dd..f78bce2 100644
--- 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
@@ -11,8 +11,12 @@ public class Main {
         // 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]));
+            if (args.length > 1) {
+                connector.useGradleUserHomeDir(new File(args[1]));
+            }
         }
 
         ProjectConnection connection = connector.connect();
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
index 20408fe..4791e4f 100644
--- 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
@@ -12,6 +12,14 @@ public class Main {
         // 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]));
+            if (args.length > 1) {
+                connector.useGradleUserHomeDir(new File(args[1]));
+            }
+        }
+
         if (args.length > 0) {
             connector.useInstallation(new File(args[0]));
         }
diff --git a/subprojects/docs/src/samples/toolingApi/build/build.gradle b/subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
similarity index 100%
rename from subprojects/docs/src/samples/toolingApi/build/build.gradle
rename to subprojects/docs/src/samples/toolingApi/runBuild/build.gradle
diff --git a/subprojects/docs/src/samples/toolingApi/build/readme.xml b/subprojects/docs/src/samples/toolingApi/runBuild/readme.xml
similarity index 100%
rename from subprojects/docs/src/samples/toolingApi/build/readme.xml
rename to subprojects/docs/src/samples/toolingApi/runBuild/readme.xml
diff --git a/subprojects/docs/src/samples/toolingApi/runBuild/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/runBuild/src/main/java/org/gradle/sample/Main.java
new file mode 100644
index 0000000..dab318f
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/runBuild/src/main/java/org/gradle/sample/Main.java
@@ -0,0 +1,43 @@
+package org.gradle.sample;
+
+import org.gradle.tooling.BuildLauncher;
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+public class Main {
+    public static void main(String[] args) {
+        // Configure the connector and create the connection
+        GradleConnector connector = GradleConnector.newConnector();
+
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+            if (args.length > 1) {
+                connector.useGradleUserHomeDir(new File(args[1]));
+            }
+        }
+
+        connector.forProjectDirectory(new File("."));
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+        }
+
+        ProjectConnection connection = connector.connect();
+        try {
+            // Configure the build
+            BuildLauncher launcher = connection.newBuild();
+            launcher.forTasks("help");
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            launcher.setStandardOutput(outputStream);
+            launcher.setStandardError(outputStream);
+
+            // Run the build
+            launcher.run();
+        } finally {
+            // Clean up
+            connection.close();
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
index 0218548..71e8fbd 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -59,14 +59,14 @@ dependencies {
 
 //START SNIPPET artifact-only
 dependencies {
-	runtime "org.groovy:groovy:1.8.6 at jar"
-    runtime group: 'org.groovy', name: 'groovy', version: '1.8.6', ext: 'jar'
+	runtime "org.groovy:groovy:1.8.8 at jar"
+    runtime group: 'org.groovy', name: 'groovy', version: '1.8.8', ext: 'jar'
 }
 //END SNIPPET artifact-only
 
 //START SNIPPET client-modules
 dependencies {
-    runtime module("org.codehaus.groovy:groovy-all:1.8.6") {
+    runtime module("org.codehaus.groovy:groovy-all:1.8.8") {
         dependency("commons-cli:commons-cli:1.0") {
             transitive = false
         }
@@ -85,7 +85,7 @@ dependencies {
 //END SNIPPET file-dependencies
 
 //START SNIPPET list-grouping
-List groovy = ["org.codehaus.groovy:groovy-all:1.8.6 at jar",
+List groovy = ["org.codehaus.groovy:groovy-all:1.8.8 at jar",
                "commons-cli:commons-cli:1.0 at jar",
                "org.apache.ant:ant:1.8.4 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 5c801d8..5c34067 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
@@ -60,7 +60,6 @@ dependencies {
 
 uploadArchives {
     repositories.mavenDeployer {
-        name = 'sshDeployer' // optional
         configuration = configurations.deployerJars
         repository(url: "scp://repos.mycompany.com/releases") {
             authentication(userName: "me", password: "myPassword")
diff --git a/subprojects/docs/src/samples/userguide/files/copy/build.gradle b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
index 3f60c29..22078a3 100644
--- a/subprojects/docs/src/samples/userguide/files/copy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
@@ -97,5 +97,6 @@ task filter(type: Copy) {
 // END SNIPPET filter-files
 
 task test {
-    dependsOn tasks.matching { it.name != 'test' }
-}
+    dependsOn tasks.withType(Copy)
+    dependsOn copyMethod
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/files/inputFiles/build.gradle b/subprojects/docs/src/samples/userguide/files/inputFiles/build.gradle
index ce7481d..51a27eb 100644
--- a/subprojects/docs/src/samples/userguide/files/inputFiles/build.gradle
+++ b/subprojects/docs/src/samples/userguide/files/inputFiles/build.gradle
@@ -1,4 +1,4 @@
-task compile(type: Compile)
+task compile(type: JavaCompile)
 
 // START SNIPPET set-input-files
 // Use a File object to specify the source directory
diff --git a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
index e44a293..bbe4d67 100644
--- a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
+++ b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
@@ -40,7 +40,7 @@ sourceSets {
 
 dependencies {
     intTestCompile 'junit:junit:4.8.2'
-    intTestRuntime 'asm:asm-all:3.3.1'
+    intTestRuntime 'org.ow2.asm:asm-all:4.0'
 }
 // END SNIPPET source-set-dependencies
 
diff --git a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
index 8615aed..24538b2 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.8.4"
+        compile "org.codehaus.groovy:groovy-all:1.8.8"
     }
 }
 
diff --git a/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties b/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties
index 0083447..1bd056f 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties
+++ b/subprojects/docs/src/samples/userguide/tutorial/properties/gradle.properties
@@ -1,4 +1,4 @@
 gradlePropertiesProp=gradlePropertiesValue
 systemPropertiesProp=shouldBeOverWrittenBySystemProp
-envPropertiesProp=shouldBeOverWrittenByEnvProp
+envProjectProp=shouldBeOverWrittenByEnvProp
 systemProp.system=systemValue
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
new file mode 100644
index 0000000..78c8158
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyInsightReport.out
@@ -0,0 +1,3 @@
+org.codehaus.groovy:groovy-all:1.8.8
+\--- projectReports:api:1.0-SNAPSHOT
+     \--- compile
\ 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 ac7bdc2..ea04b05 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
@@ -10,13 +10,13 @@ Project :api - The shared API for the application
 ------------------------------------------------------------
 
 compile
-\--- org.codehaus.groovy:groovy-all:1.8.4 [default]
+\--- org.codehaus.groovy:groovy-all:1.8.8
 
 ------------------------------------------------------------
 Project :webapp - The Web application implementation
 ------------------------------------------------------------
 
 compile
-+--- projectReports:api:1.0-SNAPSHOT [compile]
-|    \--- org.codehaus.groovy:groovy-all:1.8.4 [default]
-\--- commons-io:commons-io:1.2 [default]
++--- projectReports:api:1.0-SNAPSHOT
+|    \--- org.codehaus.groovy:groovy-all:1.8.8
+\--- commons-io:commons-io:1.2
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
index 7912ac5..9b537c0 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
@@ -19,7 +19,8 @@ webapp:libs - Builds the JAR [api:libs]
 
 Help tasks
 ----------
-dependencies - Displays the dependencies of root project 'projectReports'.
+dependencies - Displays all dependencies declared in root project 'projectReports'.
+dependencyInsight - Displays the insight into a specific dependency in 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'.
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListReport.out b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
index 61abbea..16ff74c 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
@@ -13,7 +13,8 @@ libs - Builds the JAR
 
 Help tasks
 ----------
-dependencies - Displays the dependencies of root project 'projectReports'.
+dependencies - Displays all dependencies declared in root project 'projectReports'.
+dependencyInsight - Displays the insight into a specific dependency in 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'.
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java b/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java
deleted file mode 100644
index 9a49462..0000000
--- a/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org;
-
-public class MyClassTest extends junit.framework.TestCase {
-    public void testDoSomething() throws Exception {
-        new MyClass().doSomething();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/gradle/MyClassTest.java b/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/gradle/MyClassTest.java
new file mode 100644
index 0000000..85aff51
--- /dev/null
+++ b/subprojects/docs/src/samples/webApplication/customised/src/test/java/org/gradle/MyClassTest.java
@@ -0,0 +1,7 @@
+package org.gradle;
+
+public class MyClassTest extends junit.framework.TestCase {
+    public void testDoSomething() throws Exception {
+        new MyClass().doSomething();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ear/ear.gradle b/subprojects/ear/ear.gradle
index d06cfc5..145c352 100644
--- a/subprojects/ear/ear.gradle
+++ b/subprojects/ear/ear.gradle
@@ -18,6 +18,7 @@ dependencies {
     groovy libraries.groovy
     compile project(':core')
     compile project(":plugins")
+    compile libraries.inject
 }
 
 useTestFixtures()
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
index d97a0f7..ae8a228 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
@@ -34,6 +34,7 @@ import org.gradle.api.plugins.PluginContainer;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.plugins.ear.descriptor.DeploymentDescriptor;
 
+import javax.inject.Inject;
 import java.util.concurrent.Callable;
 
 /**
@@ -49,11 +50,17 @@ public class EarPlugin implements Plugin<ProjectInternal> {
 
     public static final String DEPLOY_CONFIGURATION_NAME = "deploy";
     public static final String EARLIB_CONFIGURATION_NAME = "earlib";
+    private final Instantiator instantiator;
+
+    @Inject
+    public EarPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
 
     public void apply(final ProjectInternal project) {
         project.getPlugins().apply(BasePlugin.class);
 
-        final EarPluginConvention earPluginConvention = project.getServices().get(Instantiator.class).newInstance(EarPluginConvention.class, project.getFileResolver());
+        final EarPluginConvention earPluginConvention = instantiator.newInstance(EarPluginConvention.class, project.getFileResolver());
         project.getConvention().getPlugins().put("ear", earPluginConvention);
         earPluginConvention.setLibDirName("lib");
         earPluginConvention.setAppDirName("src/main/application");
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
index 36a499c..1dc74fc 100644
--- 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
@@ -19,13 +19,15 @@ import groovy.xml.QName
 import org.gradle.api.Action
 import org.gradle.api.UncheckedIOException
 import org.gradle.api.XmlProvider
+import org.gradle.api.internal.DomNode
+import org.gradle.api.internal.ErroringAction
+import org.gradle.api.internal.IoActions
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.file.FileResolver
 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
@@ -183,21 +185,13 @@ class DefaultDeploymentDescriptor implements DeploymentDescriptor {
     }
 
     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()
+        IoActions.writeFile(fileResolver.resolve(path), new ErroringAction<Writer>() {
+            @Override
+            void doExecute(Writer writer) {
+                writeTo(writer);
             }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e)
-        }
+        })
+        return this;
     }
 
     public DefaultDeploymentDescriptor writeTo(Writer writer) {
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
index 898c6e7..c374a1d 100644
--- a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
@@ -33,6 +33,7 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.internal.reflect.Instantiator
 
 /**
  * @author David Gileadi
@@ -58,7 +59,7 @@ class EarPluginTest {
     @Before
     public void setUp() {
         project = HelperUtil.createRootProject()
-        earPlugin = new EarPlugin()
+        earPlugin = new EarPlugin(project.services.get(Instantiator))
     }
 
     @Test public void appliesBasePluginAndAddsConvention() {
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
index fc2f7d5..1a4746f 100644
--- a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarTest.groovy
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarTest.groovy
@@ -30,10 +30,7 @@ class EarTest extends AbstractArchiveTaskTest {
 
     Ear ear
 
-    Map filesFromDepencencyManager
-
     @Before public void setUp() {
-        super.setUp()
         ear = createTask(Ear)
         configure(ear)
     }
diff --git a/subprojects/ide/ide.gradle b/subprojects/ide/ide.gradle
index 4fc3cef..1e4eab0 100644
--- a/subprojects/ide/ide.gradle
+++ b/subprojects/ide/ide.gradle
@@ -22,9 +22,10 @@ dependencies {
     compile project(':ear')
     compile project(':toolingApi')
     compile libraries.slf4j_api
+    compile libraries.inject
 
     testCompile libraries.xmlunit
     testCompile project(':coreImpl')
 }
 
-useTestFixtures()
\ No newline at end of file
+useTestFixtures()
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 1c956bd..9c79e0d 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,9 +17,8 @@
 
 package org.gradle.plugins.ide
 
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.util.TestFile
 
 abstract class AbstractIdeIntegrationTest extends AbstractIntegrationTest {
@@ -49,10 +48,6 @@ abstract class AbstractIdeIntegrationTest extends AbstractIntegrationTest {
         buildFile.parentFile.file("src/main/resources").createDir()
     }
 
-    protected MavenRepository getMavenRepo() {
-        return new MavenRepository(getFile([:], 'repo'))
-    }
-
     protected ExecutionResult runIdeaTask(buildScript) {
         return runTask("idea", buildScript)
     }
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 69a6703..ece8c73 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
@@ -147,7 +147,7 @@ eclipse {
         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"))
+        libraries[0].assertHasJavadoc(file("maven-repo/coolGroup/niceArtifact/1.0/niceArtifact-1.0-javadoc.jar"))
     }
 
     @Test
@@ -631,4 +631,36 @@ dependencies {
         libraries[1].assertHasJar(file('unresolved dependency - i.dont Exist 1.0'))
         libraries[2].assertHasJar(localJar)
     }
+
+    @Test
+    void addsScalaIdeClasspathContainerAndRemovesLibrariesDuplicatedByContainer() {
+        //given
+        def otherLib = mavenRepo.module('other', 'lib', '3.0').publish().artifactFile
+
+        //when
+        runEclipseTask """
+apply plugin: 'scala'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools "org.scala-lang:scala-compiler:2.9.2"
+
+    compile "org.scala-lang:scala-library:2.9.2"
+    runtime "org.scala-lang:scala-swing:2.9.1"
+    testCompile "org.scala-lang:scala-dbc:2.9.0"
+    testRuntime "other:lib:3.0"
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 1
+        libraries[0].assertHasJar(otherLib)
+        assert classpath.containers == ['org.eclipse.jdt.launching.JRE_CONTAINER', 'org.scala-ide.sdt.launching.SCALA_CONTAINER']
+    }
 }
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
index ccdf7e4..942247b 100644
--- 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
@@ -15,8 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.integtests.fixtures.HttpServer
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.server.http.HttpServer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -26,8 +26,6 @@ class EclipseClasspathRemoteResolutionIntegrationTest extends AbstractEclipseInt
     @Rule public final HttpServer server = new HttpServer()
     @Rule public final TestResources testResources = new TestResources()
 
-    String content
-
     @Before
     void "setup"() {
         distribution.requireOwnUserHomeDir()
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 6083bee..74a27d8 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
@@ -83,16 +83,15 @@ sourceSets {
 
     @Test
     void canHandleCircularModuleDependencies() {
-        def repoDir = file("repo")
-        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
-        def artifact2 = maven(repoDir).module("myGroup", "myArtifact2").dependsOn("myArtifact1").publish().artifactFile
+        def artifact1 = mavenRepo.module("myGroup", "myArtifact1", "1.0").dependsOn("myGroup", "myArtifact2", "1.0").publish().artifactFile
+        def artifact2 = mavenRepo.module("myGroup", "myArtifact2", "1.0").dependsOn("myGroup", "myArtifact1", "1.0").publish().artifactFile
 
         runEclipseTask """
 apply plugin: "java"
 apply plugin: "eclipse"
 
 repositories {
-    maven { url "${repoDir.toURI()}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 dependencies {
@@ -191,16 +190,15 @@ tasks.eclipse << {
 
     @Test
     void respectsPerConfigurationExcludes() {
-        def repoDir = file("repo")
-        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
-        maven(repoDir).module("myGroup", "myArtifact2").publish()
+        def artifact1 = mavenRepo.module("myGroup", "myArtifact1", "1.0").dependsOn("myGroup", "myArtifact2", "1.0").publish().artifactFile
+        mavenRepo.module("myGroup", "myArtifact2", "1.0").publish()
 
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
 repositories {
-    maven { url "${repoDir.toURI()}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 configurations {
@@ -217,16 +215,15 @@ dependencies {
 
     @Test
     void respectsPerDependencyExcludes() {
-        def repoDir = file("repo")
-        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
-        maven(repoDir).module("myGroup", "myArtifact2").publish()
+        def artifact1 = mavenRepo.module("myGroup", "myArtifact1", "1.0").dependsOn("myGroup", "myArtifact2", "1.0").publish().artifactFile
+        mavenRepo.module("myGroup", "myArtifact2", "1.0").publish()
 
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
 repositories {
-    maven { url "${repoDir.toURI()}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 dependencies {
@@ -349,15 +346,14 @@ dependencies {
     @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()
+        mavenRepo.module("some", "lib", "1.0").publish()
 
         runEclipseTask """
 apply plugin: "java"
 apply plugin: "eclipse"
 
 repositories {
-    maven { url "${repoDir.toURI()}" }
+    maven { url "${mavenRepo}" }
 }
 
 dependencies {
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 2293209..7d41b65 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
@@ -16,6 +16,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.Project
+import org.gradle.api.artifacts.Dependency
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.plugins.GroovyBasePlugin
 import org.gradle.api.plugins.JavaBasePlugin
@@ -30,6 +31,8 @@ import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
 import org.gradle.plugins.ide.eclipse.model.EclipseModel
 import org.gradle.plugins.ide.internal.IdePlugin
 
+import javax.inject.Inject
+
 /**
  * <p>A plugin which generates Eclipse files.</p>
  *
@@ -41,8 +44,14 @@ class EclipsePlugin extends IdePlugin {
     static final String ECLIPSE_CP_TASK_NAME = "eclipseClasspath"
     static final String ECLIPSE_JDT_TASK_NAME = "eclipseJdt"
 
+    private final Instantiator instantiator
     EclipseModel model
 
+    @Inject
+    EclipsePlugin(Instantiator instantiator) {
+        this.instantiator = instantiator
+    }
+
     @Override protected String getLifecycleTaskName() {
         return 'eclipse'
     }
@@ -108,7 +117,7 @@ class EclipsePlugin extends IdePlugin {
     }
 
     private void configureEclipseClasspath(Project project) {
-        model.classpath = project.services.get(Instantiator).newInstance(EclipseClasspath, project)
+        model.classpath = instantiator.newInstance(EclipseClasspath, project)
         model.classpath.conventionMapping.defaultOutputDir = { new File(project.projectDir, 'bin') }
 
         project.plugins.withType(JavaBasePlugin) {
@@ -135,6 +144,19 @@ class EclipsePlugin extends IdePlugin {
                         project.sourceSets.main.output.dirs + project.sourceSets.test.output.dirs
                     }
                 }
+
+                project.plugins.withType(ScalaBasePlugin) {
+                    classpath.containers 'org.scala-ide.sdt.launching.SCALA_CONTAINER'
+
+                    // exclude the dependencies already provided by SCALA_CONTAINER; prevents problems with Eclipse Scala plugin
+                    project.gradle.projectsEvaluated {
+                        def provided = ["scala-library", "scala-swing", "scala-dbc"]
+                        def dependencies = classpath.plusConfigurations.collectMany { it.allDependencies }.findAll { it.name in provided }
+                        if (!dependencies.empty) {
+                            classpath.minusConfigurations += project.configurations.detachedConfiguration(dependencies as Dependency[])
+                        }
+                    }
+                }
             }
         }
     }
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
index f4a5101..373d7e4 100644
--- 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
@@ -27,6 +27,8 @@ import org.gradle.plugins.ide.eclipse.model.Facet.FacetType
 import org.gradle.plugins.ide.internal.IdePlugin
 import org.gradle.plugins.ide.eclipse.model.*
 
+import javax.inject.Inject
+
 /**
  * @author: Szczepan Faber, created at: 6/28/11
  */
@@ -40,11 +42,17 @@ class EclipseWtpPlugin extends IdePlugin {
         return "eclipseWtp"
     }
 
+    private final Instantiator instantiator
     EclipseWtp eclipseWtpModel
 
+    @Inject
+    EclipseWtpPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator
+    }
+
     @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)
+        delegatePlugin.model.wtp = instantiator.newInstance(EclipseWtp, delegatePlugin.model.classpath)
         eclipseWtpModel = delegatePlugin.model.wtp
 
         lifecycleTask.description = 'Generates Eclipse wtp configuration files.'
@@ -168,8 +176,10 @@ class EclipseWtpPlugin extends IdePlugin {
                 //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))] }
+                    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")] }
                 }
@@ -186,12 +196,14 @@ class EclipseWtpPlugin extends IdePlugin {
                     //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")] }
+                    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))]
+                                    new Facet(FacetType.installed, "jst.utility", "1.0"), new Facet(FacetType.installed, "jst.java", toJavaFacetVersion(otherProject.sourceCompatibility))]
                         }
                     }
                 }
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 85c3df9..d8abbe3 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
@@ -24,10 +24,12 @@ import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 import org.gradle.util.ConfigureUtil
 
 /**
- * Enables fine-tuning classpath details (.classpath file) of the Eclipse plugin
+ * The build path settings for the generated Eclipse project. Used by the
+ * {@link org.gradle.plugins.ide.eclipse.GenerateEclipseClasspath} task to generate an Eclipse .classpath file.
  * <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!
+ * The following example demonstrates the various configuration options.
+ * Keep in mind that all properties have sensible defaults; only configure them explicitly
+ * if the defaults don't match your needs.
  *
  * <pre autoTested=''>
  * apply plugin: 'java'
@@ -39,18 +41,17 @@ import org.gradle.util.ConfigureUtil
  * }
  *
  * eclipse {
- *
  *   //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 tweak the classpath of the eclipse project by adding extra configurations:
+ *     //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
+ *     //if you don't want some classpath entries 'exported' in Eclipse
  *     noExportConfigurations += configurations.provided
  *
  *     //if you want to append extra containers:
@@ -59,18 +60,18 @@ import org.gradle.util.ConfigureUtil
  *     //customizing the classes output directory:
  *     defaultOutputDir = file('build-eclipse')
  *
- *     //default settings for dependencies sources/javadoc download:
+ *     //default settings for downloading sources and Javadoc:
  *     downloadSources = true
  *     downloadJavadoc = false
  *   }
  * }
  * </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
+ * For tackling edge cases, users can perform advanced configuration on the resulting XML file.
+ * It is also possible to affect the way that the Eclipse plugin merges the existing configuration
  * via beforeMerged and whenMerged closures.
  * <p>
- * beforeMerged and whenMerged closures receive {@link Classpath} object
+ * The beforeMerged and whenMerged closures receive a {@link Classpath} object.
  * <p>
  * Examples of advanced configuration:
  *
@@ -108,47 +109,46 @@ import org.gradle.util.ConfigureUtil
 class EclipseClasspath {
 
     /**
-     * The source sets to be added to the classpath.
+     * The source sets to be added.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     Iterable<SourceSet> sourceSets
 
     /**
-     * The configurations which files are to be transformed into classpath entries.
+     * The configurations whose files are to be added as classpath entries.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     Collection<Configuration> plusConfigurations = []
 
     /**
-     * The configurations which files are to be excluded from the classpath entries.
+     * The configurations whose files are to be excluded from the classpath entries.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     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}
+     * A subset of {@link #plusConfigurations} whose files are not to be exported to downstream Eclipse projects.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     Collection<Configuration> noExportConfigurations = []
 
     /**
-     * Containers to be added to the classpath
+     * The classpath containers to be added.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     Set<String> containers = new LinkedHashSet<String>()
 
     /**
-     * Adds containers to the .classpath.
+     * Further classpath containers to be added.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      *
-     * @param containers the container names to be added to the .classpath.
+     * @param containers the classpath containers to be added
      */
     void containers(String... containers) {
         assert containers != null
@@ -156,41 +156,39 @@ class EclipseClasspath {
     }
 
     /**
-     * The default output directory where eclipse puts compiled classes
+     * The default output directory where Eclipse puts compiled classes.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     File defaultOutputDir
 
     /**
-     * Whether to download and add sources associated with the dependency jars. Defaults to true.
+     * Whether to download and associate source Jars with the dependency Jars. Defaults to true.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     boolean downloadSources = true
 
     /**
-     * Whether to download and add javadocs associated with the dependency jars. Defaults to false.
+     * Whether to download and associate Javadoc Jars with the dependency Jars. Defaults to false.
      * <p>
-     * For example see docs for {@link EclipseClasspath}
+     * See {@link EclipseClasspath} for an example.
      */
     boolean downloadJavadoc = false
 
     /**
-     * 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}
+     * Enables advanced configuration like tinkering with the output XML or affecting the way
+     * that the contents of an existing .classpath file is merged with Gradle build information.
+     * The object passed to the whenMerged{} and beforeMerged{} closures is of type {@link Classpath}.
      * <p>
-     *
-     * For example see docs for {@link EclipseProject}
+     * See {@link EclipseProject} for an example.
      */
     void file(Closure closure) {
         ConfigureUtil.configure(closure, file)
     }
 
     /**
-     * See {@link #file(Closure)}
+     * See {@link #file(Closure)}.
      */
     XmlFileContentMerger file
 
@@ -207,7 +205,7 @@ class EclipseClasspath {
     }
 
     /**
-     * Calculates, resolves & returns dependency entries of this classpath
+     * Calculates, resolves and returns dependency entries of this classpath.
      */
     public List<ClasspathEntry> resolveDependencies() {
         def entries = new ClasspathFactory().createEntries(this)
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 e7333bb..5dd379b 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
@@ -19,7 +19,7 @@ import org.gradle.util.ConfigureUtil
 
 /**
  * DSL-friendly model of the Eclipse project information.
- * First point of entry when it comes to customizing the eclipse generation
+ * First point of entry for customizing Eclipse project generation.
  *
  * <pre autoTested=''>
  * apply plugin: 'java'
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 0d3776c..f9d7feb 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
@@ -25,6 +25,8 @@ import org.gradle.plugins.ide.idea.internal.IdeaNameDeduper
 import org.gradle.plugins.ide.internal.IdePlugin
 import org.gradle.plugins.ide.idea.model.*
 
+import javax.inject.Inject
+
 /**
  * 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.
@@ -33,8 +35,14 @@ import org.gradle.plugins.ide.idea.model.*
  */
 class IdeaPlugin extends IdePlugin {
 
+    private final Instantiator instantiator
     IdeaModel model
 
+    @Inject
+    IdeaPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator
+    }
+
     @Override protected String getLifecycleTaskName() {
         return 'idea'
     }
@@ -43,7 +51,7 @@ class IdeaPlugin extends IdePlugin {
         lifecycleTask.description = 'Generates IDEA project files (IML, IPR, IWS)'
         cleanTask.description = 'Cleans IDEA project files (IML, IPR)'
 
-        model= project.extensions.create("idea", IdeaModel)
+        model = project.extensions.create("idea", IdeaModel)
 
         configureIdeaWorkspace(project)
         configureIdeaProject(project)
@@ -79,7 +87,7 @@ class IdeaPlugin extends IdePlugin {
     private configureIdeaModule(Project project) {
         def task = project.task('ideaModule', description: 'Generates IDEA module files (IML)', type: GenerateIdeaModule) {
             def iml = new IdeaModuleIml(xmlTransformer, project.projectDir)
-            module = services.get(Instantiator).newInstance(IdeaModule, project, iml)
+            module = instantiator.newInstance(IdeaModule, project, iml)
 
             model.module = module
 
@@ -106,7 +114,7 @@ class IdeaPlugin extends IdePlugin {
         if (isRoot(project)) {
             def task = project.task('ideaProject', description: 'Generates IDEA project file (IPR)', type: GenerateIdeaProject) {
                 def ipr = new XmlFileContentMerger(xmlTransformer)
-                ideaProject = services.get(Instantiator).newInstance(IdeaProject, ipr)
+                ideaProject = instantiator.newInstance(IdeaProject, ipr)
 
                 model.project = ideaProject
 
@@ -152,10 +160,12 @@ 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
-            ] }
+            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/tooling/internal/provider/BuildModelAction.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
index 4ac8e9e..20da9c3 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
@@ -17,7 +17,10 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
+import org.gradle.api.internal.GradleInternal;
 import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.ModelConfigurationListener;
+import org.gradle.initialization.TasksCompletionListener;
 import org.gradle.tooling.internal.protocol.ProjectVersion3;
 
 import java.util.List;
@@ -25,17 +28,23 @@ import java.util.List;
 import static java.util.Arrays.asList;
 
 public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
-    private ModelBuildingAdapter modelBuildingAdapter;
+    private final BuildsModel builder;
+    private final boolean runTasks;
+    private ProjectVersion3 model;
 
-    public BuildModelAction(Class<? extends ProjectVersion3> type) {
+    public BuildModelAction(Class<?> type, boolean runTasks) {
+        this.runTasks = runTasks;
         List<? extends BuildsModel> modelBuilders = asList(
-                new EclipseModelBuilder(), new IdeaModelBuilder(),
-                new GradleProjectBuilder(), new BasicIdeaModelBuilder(),
-                new MigrationModelBuilder());
+                new NullResultBuilder(),
+                new EclipseModelBuilder(),
+                new IdeaModelBuilder(),
+                new GradleProjectBuilder(),
+                new BasicIdeaModelBuilder(),
+                new ProjectOutcomesModelBuilder());
 
         for (BuildsModel builder : modelBuilders) {
             if (builder.canBuild(type)) {
-                modelBuildingAdapter = new ModelBuildingAdapter(builder);
+                this.builder = builder;
                 return;
             }
         }
@@ -44,11 +53,25 @@ public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
     }
 
     public BuildResult run(GradleLauncher launcher) {
-        launcher.addListener(modelBuildingAdapter);
-        return launcher.getBuildAnalysis();
+
+        if (runTasks) {
+            launcher.addListener(new TasksCompletionListener() {
+                public void onTasksFinished(GradleInternal gradle) {
+                    model = builder.buildAll(gradle);
+                }
+            });
+            return launcher.run();
+        } else {
+            launcher.addListener(new ModelConfigurationListener() {
+                public void onConfigure(GradleInternal gradle) {
+                    model = builder.buildAll(gradle);
+                }
+            });
+            return launcher.getBuildAnalysis();
+        }
     }
 
     public ProjectVersion3 getResult() {
-        return modelBuildingAdapter.getProject();
+        return model;
     }
 }
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
index 846cb92..9947d1f 100644
--- 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
@@ -97,7 +97,8 @@ public class EclipseModelBuilder implements BuildsModel {
         final List<EclipseSourceDirectoryVersion1> sourceDirectories = new LinkedList<EclipseSourceDirectoryVersion1>();
 
         for (ClasspathEntry entry : entries) {
-            //TODO SF find out why we leave out Variables here.
+            //we don't handle Variables at the moment because users didn't request it yet
+            //and it would probably push us to add support in the tooling api to retrieve the variable mappings.
             if (entry instanceof Library) {
                 AbstractLibrary library = (AbstractLibrary) entry;
                 final File file = library.getLibrary().getFile();
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.java
new file mode 100644
index 0000000..24b7e3c
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/FileOutcomeIdentifier.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.tooling.internal.provider;
+
+/**
+ * This should not be used as a type in the model. It's just a container for known
+ * {@link org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome#getTypeIdentifier()} values.
+ */
+public enum FileOutcomeIdentifier {
+    ZIP_ARTIFACT("artifact.zip"),
+    JAR_ARTIFACT("artifact.jar"),
+    WAR_ARTIFACT("artifact.war"),
+    EAR_ARTIFACT("artifact.ear"),
+    TAR_ARTIFACT("artifact.tar"),
+    ARCHIVE_ARTIFACT("artifact.archive"), // We know it's an archive, but not what kind of archive
+    UNKNOWN_ARTIFACT("artifact.unknown"); // We know it's an artifact, but that's all we know for sure
+
+    private String typeIdentifier;
+
+    FileOutcomeIdentifier(String typeIdentifier) {
+        this.typeIdentifier = typeIdentifier;
+    }
+
+    public String getTypeIdentifier() {
+        return typeIdentifier;
+    }
+}
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
index f8ea32d..b42045d 100644
--- 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
@@ -93,7 +93,7 @@ public class IdeaModelBuilder implements BuildsModel {
                         .setExported(d.getExported());
 
                 if (d.getModuleVersion() != null) {
-                    defaultDependency.setExternalGradleModule(new DefaultGradleModuleVersion(d.getModuleVersion()));
+                    defaultDependency.setGradleModuleVersion(new DefaultGradleModuleVersion(d.getModuleVersion()));
                 }
                 dependencies.add(defaultDependency);
             } else if (dependency instanceof ModuleDependency) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java
deleted file mode 100644
index 4d99897..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.collect.Sets;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.tasks.testing.Test;
-import org.gradle.tooling.internal.migration.DefaultArchive;
-import org.gradle.tooling.internal.migration.DefaultProjectOutput;
-import org.gradle.tooling.internal.migration.DefaultTestResult;
-import org.gradle.tooling.internal.protocol.InternalProjectOutput;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.model.internal.migration.ProjectOutput;
-import org.gradle.tooling.model.internal.migration.TaskOutput;
-
-import java.util.Set;
-
-public class MigrationModelBuilder implements BuildsModel {
-    public boolean canBuild(Class<?> type) {
-        return type == InternalProjectOutput.class;
-    }
-
-    public ProjectVersion3 buildAll(GradleInternal gradle) {
-        return buildProjectOutput(gradle.getRootProject(), null);
-
-    }
-
-    private DefaultProjectOutput buildProjectOutput(Project project, ProjectOutput parent) {
-        Set<TaskOutput> taskOutputs = Sets.newHashSet();
-        addArchives(project, taskOutputs);
-        addTestResults(project, taskOutputs);
-
-        DefaultProjectOutput projectOutput = new DefaultProjectOutput(project.getName(),
-                project.getPath(), project.getDescription(), project.getProjectDir(), taskOutputs, parent);
-        for (Project child : project.getChildProjects().values()) {
-            projectOutput.addChild(buildProjectOutput(child, projectOutput));
-        }
-
-        return projectOutput;
-    }
-
-    private void addArchives(Project project, Set<TaskOutput> taskOutputs) {
-        Configuration configuration = project.getConfigurations().findByName("archives");
-        if (configuration != null) {
-            for (PublishArtifact artifact : configuration.getArtifacts()) {
-                taskOutputs.add(new DefaultArchive(artifact.getFile()));
-            }
-        }
-    }
-
-    private void addTestResults(Project project, Set<TaskOutput> taskOutputs) {
-        Set<Test> testTasks = project.getTasks().withType(Test.class);
-        for (Test task : testTasks) {
-            taskOutputs.add(new DefaultTestResult(task.getTestResultsDir()));
-        }
-    }
-}
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
deleted file mode 100644
index a011eca..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuildingAdapter.java
+++ /dev/null
@@ -1,42 +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.internal.GradleInternal;
-import org.gradle.initialization.ModelConfigurationListener;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-
-/**
- * @author Szczepan Faber, @date: 25.03.11
- */
-public class ModelBuildingAdapter implements ModelConfigurationListener {
-
-    private final BuildsModel builder;
-    private ProjectVersion3 eclipseProject;
-
-    public ModelBuildingAdapter(BuildsModel builder) {
-        this.builder = builder;
-    }
-
-    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/NullResultBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/NullResultBuilder.java
new file mode 100644
index 0000000..752770d
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/NullResultBuilder.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.tooling.internal.provider;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+
+public class NullResultBuilder implements BuildsModel {
+    public boolean canBuild(Class<?> type) {
+        return type.equals(Void.class);
+    }
+
+    public ProjectVersion3 buildAll(GradleInternal gradle) {
+        return null;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ProjectOutcomesModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ProjectOutcomesModelBuilder.java
new file mode 100644
index 0000000..eaeaf58
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ProjectOutcomesModelBuilder.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.tooling.internal.provider;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.tooling.internal.outcomes.DefaultProjectOutcomes;
+import org.gradle.tooling.internal.protocol.InternalProjectOutcomes;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
+
+import java.util.List;
+
+public class ProjectOutcomesModelBuilder implements BuildsModel {
+
+    private final PublishArtifactToFileBuildOutcomeTransformer artifactTransformer = new PublishArtifactToFileBuildOutcomeTransformer();
+
+    public boolean canBuild(Class<?> type) {
+        return type == InternalProjectOutcomes.class;
+    }
+
+    public ProjectVersion3 buildAll(GradleInternal gradle) {
+        return buildProjectOutput(gradle.getRootProject(), null);
+    }
+
+    private DefaultProjectOutcomes buildProjectOutput(Project project, ProjectOutcomes parent) {
+        DefaultProjectOutcomes projectOutput = new DefaultProjectOutcomes(project.getName(), project.getPath(),
+                project.getDescription(), project.getProjectDir(), getFileOutcomes(project), parent);
+        for (Project child : project.getChildProjects().values()) {
+            projectOutput.addChild(buildProjectOutput(child, projectOutput));
+        }
+        return projectOutput;
+    }
+
+    private DomainObjectSet<GradleFileBuildOutcome> getFileOutcomes(Project project) {
+        List<GradleFileBuildOutcome> fileBuildOutcomes = Lists.newArrayList();
+        addArtifacts(project, fileBuildOutcomes);
+        return new ImmutableDomainObjectSet<GradleFileBuildOutcome>(fileBuildOutcomes);
+    }
+
+    private void addArtifacts(Project project, List<GradleFileBuildOutcome> outcomes) {
+        Configuration configuration = project.getConfigurations().findByName("archives");
+        if (configuration != null) {
+            for (PublishArtifact artifact : configuration.getArtifacts()) {
+                GradleFileBuildOutcome outcome = artifactTransformer.transform(artifact, project);
+                outcomes.add(outcome);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformer.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformer.java
new file mode 100644
index 0000000..4c7c445
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformer.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.tooling.internal.provider;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.tasks.bundling.*;
+import org.gradle.plugins.ear.Ear;
+import org.gradle.tooling.internal.outcomes.DefaultGradleFileBuildOutcome;
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
+
+import java.net.URI;
+import java.util.Set;
+
+import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*;
+
+public class PublishArtifactToFileBuildOutcomeTransformer {
+
+    public GradleFileBuildOutcome transform(PublishArtifact artifact, Project project) {
+        String id = getId(artifact, project);
+        String taskPath = getTaskPath(artifact);
+        String description = getDescription(artifact);
+        String typeIdentifier = getTypeIdentifier(artifact);
+
+        return new DefaultGradleFileBuildOutcome(id, description, taskPath, artifact.getFile(), typeIdentifier);
+    }
+
+    private String getId(PublishArtifact artifact, Project project) {
+        // Assume that each artifact points to a unique file, and use the relative path from the project as the id
+        URI artifactUri = artifact.getFile().toURI();
+        URI projectDirUri = project.getProjectDir().toURI();
+        URI relativeUri = projectDirUri.relativize(artifactUri);
+        return relativeUri.getPath();
+    }
+
+    private String getDescription(PublishArtifact artifact) {
+        return String.format("Publish artifact '%s'", artifact.toString());
+    }
+
+    private String getTypeIdentifier(PublishArtifact artifact) {
+        if (artifact instanceof ArchivePublishArtifact) {
+            ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) artifact;
+            AbstractArchiveTask task = publishArtifact.getArchiveTask();
+
+            // There is an inheritance hierarchy in play here, so the order
+            // of the clauses is very important.
+
+            if (task instanceof War) {
+                return WAR_ARTIFACT.getTypeIdentifier();
+            } else if (task instanceof Ear) {
+                return EAR_ARTIFACT.getTypeIdentifier();
+            } else if (task instanceof Jar) {
+                return JAR_ARTIFACT.getTypeIdentifier();
+            } else if (task instanceof Zip) {
+                return ZIP_ARTIFACT.getTypeIdentifier();
+            } else if (task instanceof Tar) {
+                return TAR_ARTIFACT.getTypeIdentifier();
+            } else {
+                // we don't know about this kind of archive task
+                return ARCHIVE_ARTIFACT.getTypeIdentifier();
+            }
+        } else {
+            // This could very well be a zip (or something else we understand), but we can't know for sure.
+            // The client may try to infer from the file extension.
+            return UNKNOWN_ARTIFACT.getTypeIdentifier();
+        }
+    }
+
+    private String getTaskPath(PublishArtifact artifact) {
+        if (artifact instanceof ArchivePublishArtifact) {
+            return ((ArchivePublishArtifact) artifact).getArchiveTask().getPath();
+        } else {
+            String taskPath = null;
+            Set<? extends Task> tasks = artifact.getBuildDependencies().getDependencies(null);
+            if (!tasks.isEmpty()) {
+                taskPath = tasks.iterator().next().getPath();
+            }
+            return taskPath;
+        }
+    }
+
+}
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 f3ec895..572fc28 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
@@ -21,6 +21,7 @@ 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.internal.reflect.Instantiator
 import org.gradle.plugins.ide.eclipse.model.BuildCommand
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
@@ -30,7 +31,7 @@ import spock.lang.Specification
  */
 class EclipsePluginTest extends Specification {
     private final DefaultProject project = HelperUtil.createRootProject()
-    private final EclipsePlugin eclipsePlugin = new EclipsePlugin()
+    private final EclipsePlugin eclipsePlugin = new EclipsePlugin(project.services.get(Instantiator))
 
     def applyToBaseProject_shouldOnlyHaveEclipseProjectTask() {
         when:
@@ -44,8 +45,8 @@ class EclipsePluginTest extends Specification {
 
     def applyToJavaProject_shouldOnlyHaveProjectAndClasspathTaskForJava() {
         when:
-        project.apply(plugin: 'java-base')
         eclipsePlugin.apply(project)
+        project.apply(plugin: 'java-base')
 
         then:
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
@@ -62,28 +63,31 @@ class EclipsePluginTest extends Specification {
     }
 
     def applyToScalaProject_shouldHaveProjectAndClasspathTaskForScala() {
+        def scalaIdeContainer = ['org.scala-ide.sdt.launching.SCALA_CONTAINER']
+
         when:
-        project.apply(plugin: 'scala-base')
         eclipsePlugin.apply(project)
+        project.apply(plugin: 'scala-base')
+        project.gradle.buildListenerBroadcaster.projectsEvaluated(project.gradle)
 
         then:
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
         checkEclipseProjectTask([new BuildCommand('org.scala-ide.sdt.core.scalabuilder')],
                 ['org.scala-ide.sdt.core.scalanature', 'org.eclipse.jdt.core.javanature'])
-        checkEclipseClasspath([])
+        checkEclipseClasspath([], scalaIdeContainer)
 
         when:
         project.apply(plugin: 'scala')
 
         then:
-        checkEclipseClasspath([project.configurations.testRuntime])
+        checkEclipseClasspath([project.configurations.testRuntime], scalaIdeContainer)
     }
 
     def applyToGroovyProject_shouldHaveProjectAndClasspathTaskForGroovy() {
         when:
-        project.apply(plugin: 'groovy-base')
         eclipsePlugin.apply(project)
+        project.apply(plugin: 'groovy-base')
 
         then:
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
@@ -133,14 +137,14 @@ class EclipsePluginTest extends Specification {
         assert eclipseProjectTask.outputFile == project.file('.project')
     }
 
-    private void checkEclipseClasspath(def configurations) {
+    private void checkEclipseClasspath(def configurations, def additionalContainers = []) {
         GenerateEclipseClasspath eclipseClasspath = project.tasks.eclipseClasspath
         assert eclipseClasspath instanceof GenerateEclipseClasspath
         assert project.tasks.eclipse.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseClasspath)
         assert eclipseClasspath.sourceSets == project.sourceSets
         assert eclipseClasspath.plusConfigurations == configurations
         assert eclipseClasspath.minusConfigurations == []
-        assert eclipseClasspath.containers == ['org.eclipse.jdt.launching.JRE_CONTAINER'] as Set
+        assert eclipseClasspath.containers == ['org.eclipse.jdt.launching.JRE_CONTAINER'] + additionalContainers as Set
         assert eclipseClasspath.outputFile == project.file('.classpath')
         assert eclipseClasspath.defaultOutputDir == new File(project.projectDir, 'bin')
     }
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
index eba13fe..5864bf4 100644
--- 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
@@ -17,6 +17,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.internal.project.DefaultProject
+import org.gradle.internal.reflect.Instantiator
 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
@@ -30,7 +31,7 @@ import spock.lang.Specification
 class EclipseWtpPluginTest extends Specification {
 
     private final DefaultProject project = HelperUtil.createRootProject()
-    private final EclipseWtpPlugin wtpPlugin = new EclipseWtpPlugin()
+    private final EclipseWtpPlugin wtpPlugin = new EclipseWtpPlugin(project.services.get(Instantiator))
 
     def "has description"() {
         when:
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 5e70852..8046c28 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	
@@ -28,7 +28,6 @@ class GenerateIdeaModuleTest extends Specification {
     DefaultProject project = HelperUtil.createRootProject()
     Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
     Project grandChildProject = HelperUtil.createChildProject(childProject, "grandChild", new File("."))
-    IdeaPlugin ideaPlugin = new IdeaPlugin()
 
     def "moduleName controls outputFile"() {
         given:
@@ -46,8 +45,8 @@ class GenerateIdeaModuleTest extends Specification {
     }
 
     private applyPluginToProjects() {
-        ideaPlugin.apply(project)
-        ideaPlugin.apply(childProject)
-        ideaPlugin.apply(grandChildProject)
+        project.apply plugin: IdeaPlugin
+        childProject.apply plugin: IdeaPlugin
+        grandChildProject.apply plugin: IdeaPlugin
     }
 }
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 dfd764f..2935806 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
@@ -149,7 +149,7 @@ class IdeaPluginTest extends Specification {
     }
 
     private applyPluginToProjects() {
-        project.apply plugin: 'idea'
-        childProject.apply plugin: 'idea'
+        project.apply plugin: IdeaPlugin
+        childProject.apply plugin: IdeaPlugin
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.groovy
new file mode 100644
index 0000000..b21daf6
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/PublishArtifactToFileBuildOutcomeTransformerTest.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.tooling.internal.provider
+
+import org.gradle.api.Task
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.plugins.ear.Ear
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.gradle.api.tasks.bundling.*
+
+import static org.gradle.tooling.internal.provider.FileOutcomeIdentifier.*
+import org.gradle.util.HelperUtil
+import org.gradle.api.Project
+
+class PublishArtifactToFileBuildOutcomeTransformerTest extends Specification {
+
+    def transformer = new PublishArtifactToFileBuildOutcomeTransformer()
+
+    Project project = HelperUtil.createRootProject()
+
+    @Unroll
+    "can create outcome for #taskClass archive artifact"(Class<? extends AbstractArchiveTask> taskClass, FileOutcomeIdentifier typeIdentifier) {
+        given:
+        AbstractArchiveTask task = Mock(taskClass)
+        PublishArtifact artifact = new ArchivePublishArtifact(task)
+
+        and:
+        _ * task.getArchivePath() >> project.file("file")
+
+        when:
+        GradleFileBuildOutcome outcome = transformer.transform(artifact, project)
+
+        then:
+        outcome.typeIdentifier == typeIdentifier.typeIdentifier
+        outcome.id == "file"
+
+        where:
+        taskClass           | typeIdentifier
+        Zip                 | ZIP_ARTIFACT
+        Jar                 | JAR_ARTIFACT
+        Ear                 | EAR_ARTIFACT
+        Tar                 | TAR_ARTIFACT
+        War                 | WAR_ARTIFACT
+        AbstractArchiveTask | ARCHIVE_ARTIFACT
+    }
+
+    def "can handle generic publish artifact"() {
+        given:
+        def task = Mock(Task)
+        def taskDependency = Mock(TaskDependency)
+        def artifact = Mock(PublishArtifact)
+
+        1 * taskDependency.getDependencies(null) >>> [[task] as Set]
+        1 * task.getPath() >> "path"
+        _ * artifact.getFile() >> project.file("file")
+        1 * artifact.getBuildDependencies() >> taskDependency
+
+        when:
+        def outcome = transformer.transform(artifact, project)
+
+        then:
+        outcome.typeIdentifier == UNKNOWN_ARTIFACT.typeIdentifier
+        outcome.taskPath == "path"
+        outcome.id == "file"
+    }
+
+
+}
diff --git a/subprojects/integ-test/integ-test.gradle b/subprojects/integ-test/integ-test.gradle
index b478b75..aa85109 100644
--- a/subprojects/integ-test/integ-test.gradle
+++ b/subprojects/integ-test/integ-test.gradle
@@ -1,5 +1,3 @@
-apply from: "$rootDir/gradle/classycle.gradle"
-
 dependencies {
     groovy libraries.groovy
 
@@ -14,23 +12,16 @@ dependencies {
 }
 
 integTestTasks.all {
-    dependsOn ':publishLocalArchives', ':binZip', ':allZip', ':srcZip', ':docs:userguideDocbook'
+    dependsOn({ rootProject.getTasksByName('publishLocalArchives', true) }, ':docs:userguideDocbook')
 
     jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m'
 
     doFirst {
         systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
         systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
-        systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
-        systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
         forkEvery = 15
-
-        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")
@@ -38,5 +29,10 @@ integTestTasks.all {
 
 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
 }
+
+parallelIntegTest {
+    systemProperty "org.gradle.userguide.samples.exclude", "multiProjectBuildSrc,multiprojectMessagesHack"
+}
+
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
new file mode 100644
index 0000000..3f269c8
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/TaskCommandLineConfigurationIntegrationSpec.groovy
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Ignore
+
+/**
+ * by Szczepan Faber, created at: 9/5/12
+ */
+class TaskCommandLineConfigurationIntegrationSpec extends AbstractIntegrationSpec {
+
+    final String someConfigurableTaskType = """
+    import org.gradle.api.internal.tasks.CommandLineOption
+
+    class SomeTask extends DefaultTask {
+        boolean first
+        String second
+
+        @CommandLineOption(options = "first", description = "configures 'first' field")
+        void setFirst(boolean first) {
+            this.first = first
+        }
+
+        @CommandLineOption(options = "second", description = "configures 'second' field")
+        void setSecond(String second) {
+            this.second = second
+        }
+
+        //more stress
+        void setSecond(Object second) {
+            this.second = second.toString()
+        }
+
+        @TaskAction
+        void renderFields() {
+            println "first=" + first + ",second=" + second
+        }
+    }"""
+
+    def "can configure task from command line in multiple projects"() {
+        given:
+        file("settings.gradle") << "include 'project2'"
+        file("build.gradle") << """
+            allprojects {
+                task someTask(type: SomeTask)
+            }
+            task task1 //extra stress
+            task task2
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        run 'someTask'
+
+        then:
+        output.contains 'first=false,second=null'
+
+        when:
+        run 'task1', 'someTask', '--first', '--second', 'hey buddy', 'task2'
+
+        then:
+        output.count('first=true,second=hey buddy') == 2
+        result.assertTasksExecuted(":task1", ":someTask", ":project2:someTask", ":task2")
+    }
+
+    def "tasks can be configured with different options"() {
+        given:
+        file("settings.gradle") << "include 'project2'"
+        file("build.gradle") << """
+            allprojects {
+                task someTask(type: SomeTask)
+            }
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        run ':someTask', '--second', 'one', ':project2:someTask', '--second', 'two'
+
+        then:
+        result.assertTasksExecuted(":someTask", ":project2:someTask")
+        output.count('second=one') == 1
+        output.count('second=two') == 1
+    }
+
+    def "tasks are configured exclusively with their options"() {
+        given:
+        file("settings.gradle") << "include 'project2'"
+        file("build.gradle") << """
+            allprojects {
+                task someTask(type: SomeTask)
+            }
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        run ':someTask', '--second', 'one', ':project2:someTask', '--first'
+
+        then:
+        result.assertTasksExecuted(":someTask", ":project2:someTask")
+        output.count('first=false,second=one') == 1 //--first flag was set only on the :project2:someTask
+        output.count('first=true,second=null') == 1 //--second option was set only on the :someTask
+    }
+
+    def "task name that matches command value is not included in execution"() {
+        given:
+        file("build.gradle") << """
+            task foo
+            task someTask(type: SomeTask)
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        run 'someTask', '--second', 'foo'
+
+        then:
+        output.contains 'second=foo'
+        result.assertTasksExecuted(":someTask") //no 'foo' task
+    }
+
+    def "multiple different tasks configured at single command line"() {
+        given:
+        file("build.gradle") << """
+            task foo
+            task someTask(type: SomeTask)
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        run 'someTask', '--second', 'foo', 'tasks', '--all'
+
+        then:
+        output.contains 'second=foo'
+        result.assertTasksExecuted(":someTask", ":tasks")
+    }
+
+    def "different tasks match name but only one accepts the option"() {
+        given:
+        file("settings.gradle") << "include 'other'"
+        file("build.gradle") << """
+            task someTask(type: SomeTask)
+            project(":other") {
+              task someTask
+            }
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        def failure = runAndFail 'someTask', '--first'
+
+        then:
+        failure.assertHasDescription("Problem configuring task :other:someTask from command line. Unknown command-line option '--first'.")
+    }
+
+    def "using an unknown option yields decent error message"() {
+        given:
+        file("build.gradle") << """
+            task foo
+            task someTask(type: SomeTask)
+            task someTask2(type: SomeTask)
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        runAndFail 'someTask', '--second', 'foo', 'someTask2', '--secon', 'bar'
+
+        then:
+        failure.assertHasDescription("Problem configuring task :someTask2 from command line. Unknown command-line option '--secon'.")
+
+        //TODO SF it's not fixable easily we would need to change some stuff in options parsing. See also ignore test method below.
+//        when:
+//        runAndFail 'someTask', '-second', 'foo'
+//
+//        then:
+//        failure.assertHasDescription("Problem configuring task :someTask from command line. Unknown command-line option '-second'.")
+
+        when:
+        runAndFail 'someTask', '--second'
+
+        then:
+        failure.assertHasDescription("Problem configuring task :someTask from command line. No argument was provided for command-line option '--second'.")
+
+        when:
+        runAndFail 'someTask', '--second', 'hey', '--second', 'buddy'
+
+        then:
+        failure.assertHasDescription("Problem configuring task :someTask from command line. Multiple arguments were provided for command-line option '--second'.")
+    }
+
+    def "single dash user error yields decent error message"() {
+        when:
+        runAndFail 'tasks', '-all'
+
+        then:
+        failure.assertHasDescription("Incorrect command line arguments: [-l, -l]. Task options require double dash, for example: 'gradle tasks --all'.")
+    }
+
+    @Ignore
+    //more work & design decisions needed
+    def "single dash error is detected in the subsequent option"() {
+        given:
+        file("build.gradle") << """
+            task someTask(type: SomeTask)
+
+            $someConfigurableTaskType
+"""
+
+        when:
+        runAndFail 'someTask', '--first', '-second', 'foo'
+
+        then:
+        failure.assertHasDescription("Incorrect command line arguments: [-l, -l]. Task options require double dash, for example: 'gradle tasks --all'.")
+    }
+
+    @Ignore
+    //some existing problems with command line interface
+    def "unfriendly behavior of command line parsing"() {
+        when:
+        run '-all'
+
+        then:
+        "should fail with a decent error, not internal error (applies to all CommandLineArgumentExceptions)"
+        "should complain that there's no '-all' option"
+
+        when:
+        run 'tasks', '-refresh-dependenciess'
+
+        then:
+        "should fail in a consistent way as with '--refresh-dependenciess'"
+    }
+}
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
deleted file mode 100644
index 7417783..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
+++ /dev/null
@@ -1,60 +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.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/GradleRunConfiguration.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy
index 02ab189..4973cab 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy
@@ -19,8 +19,8 @@ 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'.
+ * Used by IDEA run configuration created as part of 'gradle idea' run.
+ * Required because dependency on 'launcher' project has scope 'test'.
  *
  * @author: Szczepan Faber, created at: 4/30/11
  */
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 b8113b9..e94d0fa 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
@@ -20,8 +20,8 @@ import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.groovy.scripts.UriScriptSource
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.HttpServer
 import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.GradleVersion
 import org.gradle.util.TestFile
 import org.junit.Before
@@ -64,7 +64,7 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
 
         def repoDir = file("repo")
         repo = maven(repoDir)
-        server.allowGet("/repo", repo.rootDir)
+        server.allowGetOrHead("/repo", repo.rootDir)
         repo.module("commons-io", "commons-io", "1.4").publish()
         repo.module("commons-lang", "commons-lang", "2.6").publish()
 
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 af06ec0..edebb8c 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
@@ -84,7 +84,7 @@ public class CommandLineIntegrationTest {
         } 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) }
+            ['basename', 'dirname', 'uname', 'which', 'bash'].each { linkToBinary(it, binDir) }
             path = binDir.absolutePath
         }
 
@@ -106,13 +106,13 @@ public class CommandLineIntegrationTest {
     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.withGradleUserHomeDir(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.withGradleUserHomeDir(null).withTasks("checkDefaultGradleUserHome").run();
     }
 
     @Test
@@ -164,7 +164,7 @@ public class CommandLineIntegrationTest {
         // 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.withGradleUserHomeDir(null).withArguments("-Dgradle.user.home=" + systemPropGradleUserHomeDir.absolutePath).withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath).withTasks("checkSystemPropertyGradleUserHomeHasPrecedence").run()
     }
 
     @Test
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
deleted file mode 100644
index 83dbfdc..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
+++ /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.integtests
-
-import org.apache.tools.ant.taskdefs.Expand
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.AntUtil
-import org.gradle.util.GradleVersion
-import org.gradle.util.TestFile
-import org.junit.Rule
-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
-    void binZipContents() {
-        TestFile binZip = dist.distributionsDir.file("gradle-$version-bin.zip")
-        binZip.usingNativeTools().unzipTo(dist.testDir)
-        TestFile contentsDir = dist.testDir.file("gradle-$version")
-
-        checkMinimalContents(contentsDir)
-
-        // Extra stuff
-        contentsDir.file('src').assertDoesNotExist()
-        contentsDir.file('samples').assertDoesNotExist()
-        contentsDir.file('docs').assertDoesNotExist()
-    }
-
-    @Test
-    void allZipContents() {
-        TestFile binZip = dist.distributionsDir.file("gradle-$version-all.zip")
-        binZip.usingNativeTools().unzipTo(dist.testDir)
-        TestFile contentsDir = dist.testDir.file("gradle-$version")
-
-        checkMinimalContents(contentsDir)
-
-        // Source
-        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/WrapperExecutor.java').assertIsFile()
-
-        // Samples
-        contentsDir.file('samples/java/quickstart/build.gradle').assertIsFile()
-
-        // Javadoc
-        contentsDir.file('docs/javadoc/index.html').assertIsFile()
-        contentsDir.file('docs/javadoc/index.html').assertContents(containsString("Gradle API ${version}"))
-        contentsDir.file('docs/javadoc/org/gradle/api/Project.html').assertIsFile()
-
-        // Groovydoc
-        contentsDir.file('docs/groovydoc/index.html').assertIsFile()
-        contentsDir.file('docs/groovydoc/org/gradle/api/Project.html').assertIsFile()
-        contentsDir.file('docs/groovydoc/org/gradle/api/tasks/bundling/Zip.html').assertIsFile()
-
-        // Userguide
-        contentsDir.file('docs/userguide/userguide.html').assertIsFile()
-        contentsDir.file('docs/userguide/userguide.html').assertContents(containsString("<h3 class=\"releaseinfo\">Version ${version}</h3>"))
-        contentsDir.file('docs/userguide/userguide_single.html').assertIsFile()
-        contentsDir.file('docs/userguide/userguide_single.html').assertContents(containsString("<h3 class=\"releaseinfo\">Version ${version}</h3>"))
-//        contentsDir.file('docs/userguide/userguide.pdf').assertIsFile()
-
-        // DSL reference
-        contentsDir.file('docs/dsl/index.html').assertIsFile()
-        contentsDir.file('docs/dsl/index.html').assertContents(containsString("<title>Gradle DSL Version ${version}</title>"))
-    }
-
-    private void checkMinimalContents(TestFile contentsDir) {
-        // Check it can be executed
-        executer.inDirectory(contentsDir).usingExecutable('bin/gradle').withTaskList().run()
-
-        // Scripts
-        contentsDir.file('bin/gradle').assertIsFile()
-        contentsDir.file('bin/gradle.bat').assertIsFile()
-
-        // Top level files
-        contentsDir.file('LICENSE').assertIsFile()
-
-        // Core libs
-        def coreLibs = contentsDir.file("lib").listFiles().findAll { it.name.startsWith("gradle-") }
-        assert coreLibs.size() == 10
-        coreLibs.each { assertIsGradleJar(it) }
-        def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
-        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"))
-        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-code-quality-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-antlr-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-announce-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-jetty-${version}.jar"))
-        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 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 @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.withDeprecationChecksDisabled().inDirectory(contentsDir).usingExecutable('gradlew').withTasks('binZip').run()
-
-        File binZip = contentsDir.file('build/distributions').listFiles()[0]
-        Expand unpack = new Expand()
-        unpack.src = binZip
-        unpack.dest = contentsDir.file('build/distributions/unzip')
-        AntUtil.execute(unpack)
-        TestFile unpackedRoot = new TestFile(contentsDir.file('build/distributions/unzip').listFiles()[0])
-
-        // Make sure the build distribution does something useful
-        unpackedRoot.file("bin/gradle").assertIsFile()
-        // todo run something with the gradle build by the source dist
-    }
-}
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
index 4674ddc..d1dd28d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionLocatorIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionLocatorIntegrationTest.groovy
@@ -36,7 +36,7 @@ class DistributionLocatorIntegrationTest extends Specification {
 
     def "locates snapshot versions"() {
         expect:
-        urlExist(locator.getDistributionFor(GradleVersion.version("1.0-milestone-7-20111216000006+0100")))
+        urlExist(locator.getDistributionFor(GradleVersion.version("1.3-20120919220026+0000")))
     }
 
     void urlExist(URI url) {
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
index 4ae6078..22c9f65 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
@@ -20,10 +20,13 @@ 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.test.fixtures.server.http.HttpServer
+import org.gradle.test.matchers.UserAgentMatcher
+import org.gradle.util.GradleVersion
 import org.gradle.util.TestFile
 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
@@ -145,7 +148,7 @@ class ListenerImpl extends BuildAdapter {
     @Test
     public void canFetchScriptViaHttp() {
         TestFile script = testFile('external.gradle')
-
+        server.expectUserAgent(UserAgentMatcher.matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
         server.expectGet('/external.gradle', script)
         server.start()
 
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 853d59d..bb818bb 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
@@ -145,7 +145,7 @@ rootProject {
         run "root"
 
         then:
-        executedTasks == [':worker', ':a:worker', ':b:worker', ':root']
+        result.assertTasksExecuted(':worker', ':a:worker', ':b:worker', ':root')
     }
 
     private def createExternalJar() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
new file mode 100644
index 0000000..84943da
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 static org.hamcrest.Matchers.containsString
+import spock.lang.IgnoreIf
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+
+public class MultiProjectDependencyIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        settingsFile << 'include "a", "b", "c", "d"'
+        buildFile << """
+allprojects {
+    apply plugin: 'java'
+
+    task copyLibs(type: Copy) {
+        from configurations.compile
+        into 'deps'
+    }
+
+    compileJava.dependsOn copyLibs
+}
+"""
+        executer.withArgument('--info')
+    }
+
+    def "project dependency c->[a,b]"() {
+        projectDependency from: 'c', to: ['a', 'b']
+        when:
+        run ':c:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c'
+        depsCopied 'a', []
+        depsCopied 'b', []
+        depsCopied 'c', ['a', 'b']
+    }
+
+    def "project dependency c->b->a"() {
+
+        projectDependency from: 'c', to: ['b']
+        projectDependency from: 'b', to: ['a']
+
+        when:
+        run ':c:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c'
+        depsCopied 'c', ['a', 'b']
+        depsCopied 'b', ['a']
+        depsCopied 'a', []
+    }
+
+    def "project dependency a->[b,c] and b->c"() {
+
+        projectDependency from: 'a', to: ['b', 'c']
+        projectDependency from: 'b', to: ['c']
+
+        when:
+        run ':a:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c'
+        depsCopied 'a', ['b', 'c']
+        depsCopied 'b', ['c']
+        depsCopied 'c', []
+    }
+
+    def "project dependency a->[b,d] and b->c->d"() {
+
+        projectDependency from: 'a', to: ['b', 'd']
+        projectDependency from: 'b', to: ['c']
+        projectDependency from: 'c', to: ['d']
+
+        when:
+        run ':a:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c', 'd'
+        depsCopied 'a', ['b', 'c', 'd']
+        depsCopied 'b', ['c', 'd']
+        depsCopied 'c', ['d']
+        depsCopied 'd', []
+    }
+
+    def "project dependency a->[b,c] and b->d and c->d"() {
+
+        projectDependency from: 'a', to: ['b', 'c']
+        projectDependency from: 'b', to: ['d']
+        projectDependency from: 'c', to: ['d']
+
+        when:
+        run ':a:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c', 'd'
+        depsCopied 'a', ['b', 'c', 'd']
+        depsCopied 'b', ['d']
+        depsCopied 'c', ['d']
+        depsCopied 'd', []
+    }
+
+    def "circular project dependency without task cycle a->b->c->a:2"() {
+
+        projectDependency from: 'a', to: ['b']
+        projectDependency from: 'b', to: ['c']
+
+        def outputValue = System.currentTimeMillis()
+
+        buildFile << """
+project(':a') {
+    task writeOutputFile << {
+        file('build').mkdirs()
+        file('build/output.txt') << "${outputValue}"
+    }
+}
+project(':c') {
+    compileJava.dependsOn ':a:writeOutputFile'
+}
+"""
+
+        when:
+        run ':a:build'
+
+        then:
+        jarsBuilt 'a', 'b', 'c'
+        depsCopied 'a', ['b', 'c']
+        depsCopied 'b', ['c']
+        depsCopied 'c', []
+
+        and:
+        file('a/build/output.txt').text == "$outputValue"
+    }
+
+    def "project dependency cycle a->b->c->a"() {
+        projectDependency from: 'a', to: ['b']
+        projectDependency from: 'b', to: ['c']
+        projectDependency from: 'c', to: ['a']
+
+        when:
+        fails ':a:build'
+
+        then:
+        failure.assertHasNoCause()
+        failure.assertThatDescription(containsString("Circular dependency between tasks. Cycle includes [task ':a:compileJava', task ':a:jar']."))
+    }
+
+    def "project dependency a->b->c->d and c fails"() {
+        projectDependency from: 'a', to: ['b']
+        projectDependency from: 'b', to: ['c']
+        projectDependency from: 'c', to: ['d']
+        failingBuild 'c'
+
+        when:
+        fails ':a:build'
+
+        then:
+        failure.assertHasCause 'failure in c'
+
+        and:
+        jarsBuilt 'd'
+        jarsNotBuilt 'a', 'b', 'c'
+    }
+
+    @IgnoreIf({GradleDistributionExecuter.systemPropertyExecuter.executeParallel})  // 'c' + 'd' _may_ be built with parallel executer
+    def "project dependency a->[b,c] and c->d and b fails"() {
+        projectDependency from: 'a', to: ['b', 'c']
+        projectDependency from: 'c', to: ['d']
+        failingBuild 'b'
+
+        when:
+        fails ':a:build'
+
+        then:
+        failure.assertHasCause 'failure in b'
+
+        and:
+        jarsNotBuilt 'a', 'b', 'c', 'd'
+    }
+
+    def "project dependency a->[b,c] and c->d and b fails with run with --continue"() {
+        projectDependency from: 'a', to: ['b', 'c']
+        projectDependency from: 'c', to: ['d']
+        failingBuild 'b'
+
+        when:
+        executer.withArgument('--continue')
+        fails ':a:build'
+
+        then:
+        failure.assertHasCause 'failure in b'
+
+        and:
+        jarsBuilt 'c', 'd'
+        jarsNotBuilt 'a', 'b'
+    }
+
+    def "project dependency a->[b,c] and both b & c fail with --continue"() {
+        projectDependency from: 'a', to: ['b', 'c']
+        failingBuild 'b'
+        failingBuild 'c'
+
+        when:
+        executer.withArgument('--continue')
+        fails ':a:build'
+
+        then:
+        failure.assertHasCause 'failure in b'
+        failure.assertHasCause 'failure in c'
+
+        and:
+        jarsNotBuilt 'a', 'b', 'c'
+    }
+
+    def projectDependency(def link) {
+        def from = link['from']
+        def to = link['to']
+
+        def dependencies = to.collect {
+            "compile project(':${it}')"
+        }.join('\n')
+
+        buildFile << """
+project(':$from') {
+    dependencies {
+        ${dependencies}
+    }
+}
+"""
+    }
+
+    def failingBuild(def project) {
+        buildFile << """
+project(':$project') {
+    task fail << {
+        throw new RuntimeException('failure in $project')
+    }
+    jar.dependsOn fail
+}
+"""
+    }
+
+    def jarsBuilt(String... projects) {
+        projects.each {
+            file("${it}/build/libs/${it}.jar").assertExists()
+        }
+    }
+
+    def jarsNotBuilt(String... projects) {
+        projects.each {
+            file("${it}/build/libs/${it}.jar").assertDoesNotExist()
+        }
+    }
+
+    def depsCopied(String project, Collection<String> deps) {
+        def depsDir = file("${project}/deps")
+        if (deps.isEmpty()) {
+            depsDir.assertDoesNotExist()
+        } else {
+            String[] depJars = deps.collect {
+                "${it}.jar"
+            }
+            depsDir.assertHasDescendants(depJars)
+        }
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..64b9490
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.test.fixtures.server.http.BlockingHttpServer
+import org.junit.Rule
+
+public class ParallelProjectExecutionIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule public final BlockingHttpServer blockingServer = new BlockingHttpServer()
+
+    def setup() {
+        blockingServer.start()
+
+        settingsFile << 'include "a", "b", "c", "d"'
+        buildFile << """
+allprojects {
+    task pingServer << {
+        URL url = new URL("http://localhost:${blockingServer.port}/" + project.path)
+        println url.openConnection().getHeaderField('RESPONSE')
+    }
+}
+"""
+        executer.withArgument('--parallel')
+        executer.withArgument('--info')
+    }
+
+    def "executes dependency project targets concurrently"() {
+
+        projectDependency from: 'a', to: ['b', 'c', 'd']
+
+        expect:
+        blockingServer.expectConcurrentExecution(':b', ':c', ':d')
+        blockingServer.expectConcurrentExecution(':a')
+
+        run ':a:pingServer'
+    }
+
+    def "executes dependency project targets concurrently where possible"() {
+
+        projectDependency from: 'a', to: ['b', 'c']
+        projectDependency from: 'b', to: ['d']
+        projectDependency from: 'c', to: ['d']
+
+        expect:
+        blockingServer.expectConcurrentExecution(':d')
+        blockingServer.expectConcurrentExecution(':b', ':c')
+        blockingServer.expectConcurrentExecution(':a')
+
+        run ':a:pingServer'
+    }
+
+    def "project dependency a->[b,c] and both b & c fail"() {
+        projectDependency from: 'a', to: ['b', 'c']
+        failingBuild 'b'
+        failingBuild 'c'
+
+        when:
+        blockingServer.expectConcurrentExecution(':b', ':c')
+
+        fails ':a:pingServer'
+
+        then:
+        failure.error =~ 'b failed'
+        failure.error =~ 'c failed'
+    }
+
+
+    def projectDependency(def link) {
+        def from = link['from']
+        def to = link['to']
+
+        def dependencies = to.collect {
+            "pingServer.dependsOn(':${it}:pingServer')"
+        }.join('\n')
+
+        buildFile << """
+project(':$from') {
+    ${dependencies}
+}
+"""
+    }
+
+    def failingBuild(def project) {
+        def failure = "$project failed"
+        buildFile << """
+project(':$project') {
+    pingServer.doLast {
+        throw new RuntimeException('$failure')
+    }
+}
+"""
+    }
+}
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 ad81927..fd5ab76 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
@@ -40,11 +40,10 @@ repositories {
     mavenCentral()
 }
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.6.0'
-    scalaTools group: 'org.scala-lang', name: 'scala-compiler', version: '2.8.1'
-    scalaTools group: 'org.scala-lang', name: 'scala-library', version: '2.8.1'
-
-    compile group: 'org.scala-lang', name: 'scala-library', version: '2.8.1'
+    groovy 'org.codehaus.groovy:groovy-all:1.8.8'
+    // scaladoc in Scala 2.9.2 requires Java 1.6
+    scalaTools 'org.scala-lang:scala-compiler:2.9.1'
+    compile 'org.scala-lang:scala-library:2.9.1'
 }
 
 sourceSets.each {
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
deleted file mode 100644
index 9ee148b..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectReportsPluginIntegrationTest.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.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/WorkerProcessIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
index e4c19d3..27b7332 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
@@ -19,17 +19,15 @@ package org.gradle.integtests;
 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.*;
 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.internal.file.TestFiles;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.internal.*;
 import org.gradle.internal.id.LongIdGenerator;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.messaging.dispatch.Dispatch;
@@ -37,7 +35,6 @@ 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.TemporaryFolder;
@@ -66,13 +63,14 @@ public class WorkerProcessIntegrationTest {
     private final TestListenerInterface listenerMock = context.mock(TestListenerInterface.class);
     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 ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment.class));
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(NativeServices.getInstance().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 DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, TestFiles.resolver(tmpDir.getTestDir()), new LongIdGenerator());
     private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
             TestListenerInterface.class);
     private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
@@ -131,7 +129,8 @@ public class WorkerProcessIntegrationTest {
         execute(worker(new RemoteProcess()), worker(new OtherRemoteProcess()));
     }
 
-    @Test @Ignore
+    @Test
+    @Ignore
     public void handlesWorkerProcessWhichCrashes() throws Throwable {
         context.checking(new Expectations() {{
             atMost(1).of(listenerMock).send("message 1", 1);
@@ -163,10 +162,10 @@ public class WorkerProcessIntegrationTest {
 
     @Test
     public void handlesWorkerProcessWhenJvmFailsToStart() throws Throwable {
-        execute(worker(new NoOpAction()).jvmArgs("--broken").expectStartFailure());
+        execute(worker(Actions.doNothing()).jvmArgs("--broken").expectStartFailure());
     }
 
-    private ChildProcess worker(Action<WorkerProcessContext> action) {
+    private ChildProcess worker(Action<? super WorkerProcessContext> action) {
         return new ChildProcess(action);
     }
 
@@ -185,11 +184,11 @@ public class WorkerProcessIntegrationTest {
         private boolean stopFails;
         private boolean startFails;
         private WorkerProcess proc;
-        private Action<WorkerProcessContext> action;
+        private Action<? super WorkerProcessContext> action;
         private List<String> jvmArgs = Collections.emptyList();
         private Action<ObjectConnection> serverAction;
 
-        public ChildProcess(Action<WorkerProcessContext> action) {
+        public ChildProcess(Action<? super WorkerProcessContext> action) {
             this.action = action;
         }
 
@@ -288,7 +287,7 @@ public class WorkerProcessIntegrationTest {
             assertThat(thisClassLoader, not(sameInstance(systemClassLoader)));
             assertThat(antClassLoader.getParent(), equalTo(systemClassLoader.getParent()));
             try {
-                assertThat(thisClassLoader.loadClass(Project.class.getName()), sameInstance((Object)Project.class));
+                assertThat(thisClassLoader.loadClass(Project.class.getName()), sameInstance((Object) Project.class));
             } catch (ClassNotFoundException e) {
                 throw new RuntimeException(e);
             }
@@ -366,21 +365,16 @@ public class WorkerProcessIntegrationTest {
         }
     }
 
-    public static class NoOpAction implements Action<WorkerProcessContext>, Serializable {
-        public void execute(WorkerProcessContext workerProcessContext) {
-        }
-    }
-
     public static class NoConnectRemoteProcess implements Action<WorkerProcessContext>, Serializable {
         private void readObject(ObjectInputStream instr) {
             System.exit(0);
         }
-        
+
         public void execute(WorkerProcessContext workerProcessContext) {
             throw new UnsupportedOperationException();
         }
     }
-    
+
     public interface TestListenerInterface {
         public void send(String message, int count);
     }
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
deleted file mode 100644
index 6596215..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
+++ /dev/null
@@ -1,172 +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.util.SetSystemProperties
-import org.gradle.util.TextUtil
-import org.junit.Rule
-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 extends AbstractIntegrationSpec {
-    @Rule HttpServer server = new HttpServer()
-    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
-    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-
-    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)
-    }
-
-    public void "has non-zero exit code on build failure"() {
-        given:
-        prepareWrapper("http://localhost:${server.port}")
-
-        expect:
-        server.allowGet("/gradlew/dist", distribution.binDistribution)
-
-        when:
-        ExecutionFailure failure = wrapperExecuter.withTasks('unknown').runWithFailure()
-        
-        then:
-        failure.assertHasDescription("Task 'unknown' not found in root project")
-    }
-
-    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'))
-    }
-
-    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}
-"""
-
-        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
index 8df532d..969c74a 100644
--- 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
@@ -18,7 +18,6 @@ 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
@@ -163,7 +162,7 @@ assert classesDir.directory
         def out = executer.withForkingExecuter().run().output
 
         then:
-        out.contains("javaHome=" + alternateJavaHome.absolutePath)
+        out.contains("javaHome=" + alternateJavaHome.canonicalPath)
     }
 
     def "jvm args from gradle properties should be used to run build"() {
@@ -175,12 +174,7 @@ 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()
+        executer.withForkingExecuter().withNoDefaultJvmArgs().run()
 
         then:
         noExceptionThrown()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
index ac6a4b1..7e0fcfe 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
@@ -16,29 +16,40 @@
 
 package org.gradle.integtests.fixture
 
+import org.gradle.integtests.fixtures.MavenFileRepository
 import org.gradle.util.TestFile
-import org.gradle.integtests.fixtures.MavenRepository
 
 class M2Installation {
+    final TestFile userHomeDir
     final TestFile userM2Directory
     final TestFile userSettingsFile
-    TestFile globalMavenDirectory = null;
+    final TestFile globalMavenDirectory
 
     public M2Installation(TestFile m2Directory) {
-        this.userM2Directory = m2Directory;
-        this.userSettingsFile = m2Directory.file("settings.xml")
+        userHomeDir = m2Directory.createDir("maven_home")
+        userM2Directory = userHomeDir.createDir(".m2")
+        userSettingsFile = userM2Directory.file("settings.xml")
+        globalMavenDirectory = userHomeDir.createDir("m2_home")
     }
 
-    MavenRepository mavenRepo() {
-        mavenRepo(userM2Directory.file("repository"))
+    MavenFileRepository mavenRepo() {
+        new MavenFileRepository(userM2Directory.file("repository"))
     }
 
-    MavenRepository mavenRepo(TestFile file) {
-        new MavenRepository(file)
+    M2Installation generateUserSettingsFile(MavenFileRepository userRepository) {
+        userSettingsFile.text = """
+<settings>
+    <localRepository>${userRepository.rootDir.absolutePath}</localRepository>
+</settings>"""
+        return this
     }
 
-    TestFile createGlobalSettingsFile(TestFile globalMavenDirectory) {
-        this.globalMavenDirectory = globalMavenDirectory;
-        globalMavenDirectory.file("conf/settings.xml").createFile()
+    M2Installation generateGlobalSettingsFile(MavenFileRepository globalRepository = mavenRepo()) {
+        def settings = globalMavenDirectory.file("conf/settings.xml").createFile()
+        settings.text = """
+<settings>
+    <localRepository>${globalRepository.rootDir.absolutePath}</localRepository>
+</settings>"""
+        return this
     }
 }
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
index 84aa66f..cd925cc 100644
--- 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests.publish.ivy
 
-import org.gradle.integtests.fixtures.IvyRepository
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class IvyEarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
@@ -44,7 +43,7 @@ dependencies {
 uploadArchives {
     repositories {
         ivy {
-            url 'ivy-repo'
+            url '${ivyRepo.uri}'
         }
     }
 }
@@ -54,7 +53,7 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def ivyModule = ivyRepo.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/IvyHttpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
index a6d6d64..a0877b3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -18,9 +18,11 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.fixtures.IvyModule
-import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyFileModule
+import org.gradle.test.fixtures.ivy.IvyModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
 import org.gradle.util.Jvm
 import org.gradle.util.TestFile
 import org.gradle.util.TextUtil
@@ -29,6 +31,8 @@ import org.junit.Rule
 import org.mortbay.jetty.HttpStatus
 import spock.lang.Unroll
 
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+
 public class IvyHttpPublishIntegrationTest extends AbstractIntegrationSpec {
     private static final String BAD_CREDENTIALS = '''
 credentials {
@@ -39,23 +43,25 @@ credentials {
     @Rule
     public final HttpServer server = new HttpServer()
 
+    @Rule ProgressLoggingFixture progressLogging
+
     private IvyModule module
 
     def setup() {
-        def repo = new IvyRepository(distribution.testFile('ivy-repo'))
-        module = repo.module("org.gradle", "publish", "2")
+        module = ivyRepo.module("org.gradle", "publish", "2")
         module.moduleDir.mkdirs()
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
     }
 
     public void canPublishToUnauthenticatedHttpRepository() {
         given:
         server.start()
-
         settingsFile << 'rootProject.name = "publish"'
         buildFile << """
 apply plugin: 'java'
 version = '2'
 group = 'org.gradle'
+
 uploadArchives {
     repositories {
         ivy {
@@ -77,8 +83,12 @@ uploadArchives {
 
         module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
         module.assertChecksumPublishedFor(module.jarFile)
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
     }
 
+
     @Unroll
     def "can publish to authenticated repository using #authScheme auth"() {
         given:
@@ -116,6 +126,10 @@ uploadArchives {
         module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
         module.assertChecksumPublishedFor(module.jarFile)
 
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
+
         where:
         authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
     }
@@ -150,7 +164,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertHasCause('Could not publish configuration: [archives]')
         failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
 
         where:
@@ -185,7 +199,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertHasCause('Could not publish configuration: [archives]')
         failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
 
         when:
@@ -196,7 +210,7 @@ uploadArchives {
 
         and:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertHasCause('Could not publish configuration: [archives]')
         failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
     }
 
@@ -308,12 +322,12 @@ uploadArchives {
         module.ivyFile.assertDoesNotExist()
     }
 
-    private void expectUpload(String path, IvyModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
+    private void expectUpload(String path, IvyFileModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
         server.expectPut(path, file, statusCode)
         server.expectPut("${path}.sha1", module.sha1File(file), statusCode)
     }
 
-    private void expectUpload(String path, IvyModule module, TestFile file, String username, String password) {
+    private void expectUpload(String path, IvyFileModule module, TestFile file, String username, String password) {
         server.expectPut(path, username, password, file)
         server.expectPut("${path}.sha1", username, password, module.sha1File(file))
     }
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
index 7e48989..fbcb054 100644
--- 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
@@ -16,7 +16,6 @@
 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"() {
@@ -42,7 +41,7 @@ dependencies {
 uploadArchives {
     repositories {
         ivy {
-            url 'ivy-repo'
+            url '${ivyRepo.uri}'
         }
     }
 }
@@ -52,9 +51,9 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def ivyModule = ivyRepo.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")
+        ivyModule.ivy.dependencies.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        ivyModule.ivy.dependencies.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
index 7a5b329..26dbd40 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
@@ -16,17 +16,14 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.IvyRepository
 import org.spockframework.util.TextUtil
 import spock.lang.Issue
 
 public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
     public void canPublishToLocalFileRepository() {
         given:
-        def repo = new IvyRepository(distribution.testFile('ivy-repo'))
-        def module = repo.module("org.gradle", "publish", "2")
+        def module = ivyRepo.module("org.gradle", "publish", "2")
 
-        def rootDir = TextUtil.escape(repo.rootDir.path)
         settingsFile << 'rootProject.name = "publish"'
         buildFile << """
 apply plugin: 'java'
@@ -35,7 +32,7 @@ group = 'org.gradle'
 uploadArchives {
     repositories {
         ivy {
-            url "${rootDir}"
+            url "${ivyRepo.uri}"
         }
     }
 }
@@ -51,6 +48,40 @@ uploadArchives {
         module.assertChecksumPublishedFor(module.jarFile)
     }
 
+    @Issue("GRADLE-2456")
+    public void generatesSHA1FileWithLeadingZeros() {
+        given:
+        def module = ivyRepo.module("org.gradle", "publish", "2")
+        byte[] jarBytes = [0, 0, 0, 5]
+        def artifactFile = file("testfile.bin")
+        artifactFile << jarBytes
+        def artifactPath = TextUtil.escape(artifactFile.path)
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin:'java'
+group = "org.gradle"
+version = '2'
+artifacts {
+        archives file: file("${artifactPath}"), name: 'testfile', type: 'bin'
+}
+
+uploadArchives {
+    repositories {
+        ivy {
+            url "${ivyRepo.uri}"
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def shaOneFile = module.moduleDir.file("testfile-2.bin.sha1")
+        shaOneFile.exists()
+        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
+
     @Issue("GRADLE-1811")
     public void canGenerateTheIvyXmlWithoutPublishing() {
         //this is more like documenting the current behavior.
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
index 9008dcb..4871778 100644
--- 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
@@ -16,13 +16,16 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.SFTPServer
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.server.sftp.SFTPServer
 import org.junit.Rule
 
 class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
 
     @Rule
     public final SFTPServer sftpServer = new SFTPServer(distribution.temporaryFolder)
+    @Rule
+    ProgressLoggingFixture progressLogging
 
     public void "can publish using SftpResolver"() {
         given:
@@ -46,12 +49,15 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
         }
         """
         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'))
+        and:
+        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/ivy-2.xml")
+        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/publish-2.jar")
     }
 
     public void "reports Authentication Errors"() {
@@ -80,7 +86,7 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
 
         then:
         failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        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/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
index cc5f52a..fdaf12a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
@@ -17,7 +17,6 @@
 package org.gradle.integtests.publish.ivy
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.IvyRepository
 
 class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
     def "publish multiple artifacts in single configuration"() {
@@ -50,7 +49,7 @@ artifacts {
 uploadPublish {
     repositories {
         ivy {
-            url "ivy-repo"
+            url "${ivyRepo.uri}"
         }
     }
 }
@@ -60,7 +59,7 @@ uploadPublish {
         run "uploadPublish"
 
         then:
-        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
         ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar1-1.9.jar", "jar2-1.9.jar")
         ivyModule.moduleDir.file("jar1-1.9.jar").bytes == file("build/libs/jar1-1.9.jar").bytes
         ivyModule.moduleDir.file("jar2-1.9.jar").bytes == file("build/libs/jar2-1.9.jar").bytes
@@ -102,7 +101,7 @@ artifacts {
 tasks.withType(Upload) {
     repositories {
         ivy {
-            url "ivy-repo"
+            url "${ivyRepo.uri}"
         }
     }
 }
@@ -112,7 +111,7 @@ tasks.withType(Upload) {
         run "uploadPublish$n"
 
         then:
-        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
         ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar$n-1.9.jar")
         ivyModule.moduleDir.file("jar$n-1.9.jar").bytes == file("build/libs/jar$n-1.9.jar").bytes
 
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
index 013f8a9..702e047 100644
--- 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests.publish.ivy
 
-import org.gradle.integtests.fixtures.IvyRepository
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class IvyWarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
@@ -43,7 +42,7 @@ dependencies {
 uploadArchives {
     repositories {
         ivy {
-            url 'ivy-repo'
+            url '${ivyRepo.uri}'
         }
     }
 }
@@ -53,7 +52,7 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def ivyModule = ivyRepo.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/maven/MavenEarProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
index d6fcd13..fa6df89 100644
--- 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests.publish.maven
 
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class MavenEarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
@@ -44,7 +43,7 @@ dependencies {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri("maven-repo"))
+            repository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -54,7 +53,7 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def mavenModule = mavenRepo.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
index 4820264..8d31424 100644
--- 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
@@ -16,7 +16,6 @@
 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"() {
@@ -43,7 +42,7 @@ dependencies {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri("maven-repo"))
+            repository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -53,7 +52,7 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def mavenModule = mavenRepo.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/MavenMultiProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
index f82c0a9..953fb7d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
@@ -17,11 +17,9 @@
 package org.gradle.integtests.publish.maven
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenRepository
 import spock.lang.Issue
 
 class MavenMultiProjectPublishIntegrationTest extends AbstractIntegrationSpec {
-    def mavenRepo = new MavenRepository(file("maven-repo"))
     def mavenModule = mavenRepo.module("org.gradle.test", "project1", "1.9")
 
     def "project dependency correctly reflected in POM if publication coordinates are unchanged"() {
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
deleted file mode 100644
index fafd2e9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenNewPublicationIntegrationTest.groovy
+++ /dev/null
@@ -1,158 +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.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/MavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
index 0e61b1a..a17b1de 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
@@ -17,8 +17,6 @@
 package org.gradle.integtests.publish.maven
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.MavenRepository
-
 // this spec documents the status quo, not a desired behavior
 class MavenPomGenerationIntegrationTest extends AbstractIntegrationSpec {
     def "how configuration of archive task affects generated POM"() {
@@ -39,7 +37,7 @@ jar {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: "file:///\$rootProject.projectDir/maven-repo")
+            repository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -49,7 +47,6 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def mavenRepo = new MavenRepository(file("maven-repo"))
         def mavenModule = mavenRepo.module("org.gradle.test", pomArtifactId, pomVersion)
         def pom = mavenModule.pom
         pom.groupId == "org.gradle.test"
@@ -80,7 +77,7 @@ jar {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: "file:///\$rootProject.projectDir/maven-repo")
+            repository(url: "${mavenRepo.uri}")
             ${deployerGroupId ? "pom.groupId = '$deployerGroupId'" : ""}
             ${deployerArtifactId ? "pom.artifactId = '$deployerArtifactId'" : ""}
             ${deployerVersion ? "pom.version = '$deployerVersion'" : ""}
@@ -94,7 +91,6 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def mavenRepo = new MavenRepository(file("maven-repo"))
         def mavenModule = mavenRepo.module(pomGroupId, pomArtifactId, pomVersion)
         def pom = mavenModule.pom
         pom.groupId == pomGroupId
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
index 37fb4ea..716242c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
@@ -16,11 +16,15 @@
 package org.gradle.integtests.publish.maven
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
 import org.junit.Rule
+import org.spockframework.util.TextUtil
+import spock.lang.Issue
 import spock.lang.Unroll
 
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+
 class MavenPublishIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
 
@@ -41,7 +45,7 @@ dependencies {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri("mavenRepo"))
+            repository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -50,10 +54,45 @@ uploadArchives {
         succeeds 'uploadArchives'
 
         then:
-        def module = repo().module('group', 'root', 1.0)
+        def module = mavenRepo.module('group', 'root', 1.0)
         module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.pom')
     }
 
+    @Issue("GRADLE-2456")
+    public void generatesSHA1FileWithLeadingZeros() {
+        given:
+        def module = mavenRepo.module("org.gradle", "publish", "2")
+        byte[] jarBytes = [0, 0, 0, 5]
+        def artifactFile = file("testfile.bin")
+        artifactFile << jarBytes
+        def artifactPath = TextUtil.escape(artifactFile.path)
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+    apply plugin:'java'
+    apply plugin: 'maven'
+    group = "org.gradle"
+    version = '2'
+    artifacts {
+        archives file: file("${artifactPath}")
+    }
+
+    uploadArchives {
+        repositories {
+            mavenDeployer {
+                repository(url: "${mavenRepo.uri}")
+            }
+        }
+    }
+    """
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def shaOneFile = module.moduleDir.file("publish-2.bin.sha1")
+        shaOneFile.exists()
+        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
+
     def "can publish a project with no main artifact"() {
         given:
         settingsFile << "rootProject.name = 'root'"
@@ -73,7 +112,7 @@ artifacts {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri('mavenRepo'))
+            repository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -82,7 +121,7 @@ uploadArchives {
         succeeds 'uploadArchives'
 
         then:
-        def module = repo().module('group', 'root', 1.0)
+        def module = mavenRepo.module('group', 'root', 1.0)
         module.assertArtifactsPublished('root-1.0-source.jar')
     }
 
@@ -112,7 +151,7 @@ artifacts {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri("mavenRepo"))
+            repository(url: uri("${mavenRepo.uri}"))
             beforeDeployment { MavenDeployment deployment ->
                 assert deployment.pomArtifact.file.isFile()
                 assert deployment.pomArtifact.name == 'root'
@@ -134,7 +173,7 @@ uploadArchives {
         succeeds 'uploadArchives'
 
         then:
-        def module = repo().module('group', 'root', 1.0)
+        def module = mavenRepo.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')
     }
 
@@ -150,7 +189,7 @@ archivesBaseName = 'test'
 uploadArchives {
     repositories {
         mavenDeployer {
-            snapshotRepository(url: uri("mavenRepo"))
+            snapshotRepository(url: "${mavenRepo.uri}")
         }
     }
 }
@@ -160,14 +199,13 @@ uploadArchives {
         succeeds 'uploadArchives'
 
         then:
-        def module = repo().module('org.gradle', 'test', '1.0-SNAPSHOT')
+        def module = mavenRepo.module('org.gradle', 'test', '1.0-SNAPSHOT')
         module.assertArtifactsPublished('test-1.0-SNAPSHOT.jar', 'test-1.0-SNAPSHOT.pom')
     }
 
     def "can publish multiple deployments with attached artifacts"() {
         given:
         server.start()
-
         settingsFile << "rootProject.name = 'someCoolProject'"
         buildFile << """
 apply plugin:'java'
@@ -209,7 +247,7 @@ uploadArchives {
 }
 """
         when:
-        def module = repo().module('org.test', 'someCoolProject')
+        def module = mavenRepo.module('org.test', 'someCoolProject')
         def moduleDir = module.moduleDir
         moduleDir.mkdirs()
 
@@ -229,7 +267,7 @@ uploadArchives {
         then:
         succeeds 'uploadArchives'
     }
-    
+
     def "can publish to an unauthenticated HTTP repository"() {
         given:
         server.start()
@@ -248,7 +286,7 @@ uploadArchives {
 }
 """
         when:
-        def module = repo().module('org.test', 'root')
+        def module = mavenRepo.module('org.test', 'root')
         def moduleDir = module.moduleDir
         moduleDir.mkdirs()
         expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.pom")
@@ -275,6 +313,7 @@ uploadArchives {
         def username = 'testuser'
         def password = 'password'
         server.start()
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().version))
         settingsFile << "rootProject.name = 'root'"
         buildFile << """
 apply plugin: 'java'
@@ -295,7 +334,7 @@ uploadArchives {
         server.authenticationScheme = authScheme
 
         and:
-        def module = repo().module('org.test', 'root')
+        def module = mavenRepo.module('org.test', 'root')
         def moduleDir = module.moduleDir
         moduleDir.mkdirs()
         expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.jar", username, password)
@@ -319,8 +358,4 @@ uploadArchives {
         server.expectPut("$path/${name}.md5", username, password, moduleDir.file("${name}.md5"))
         server.expectPut("$path/${name}.sha1", username, password, moduleDir.file("${name}.sha1"))
     }
-
-    def MavenRepository repo() {
-        new MavenRepository(distribution.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
index b7c8846..aed7547 100644
--- 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
@@ -17,7 +17,6 @@
 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 {
@@ -25,7 +24,6 @@ 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"
@@ -43,7 +41,7 @@ class MavenPublishRespectsPomConfigurationTest extends AbstractIntegrationSpec {
         uploadArchives {
             repositories {
                 mavenDeployer {
-                    repository(url: uri("file://${mvnRepo.absolutePath}"))
+                    repository(url: uri("${mavenRepo.uri}"))
                 }
             }
         }
@@ -75,9 +73,9 @@ class MavenPublishRespectsPomConfigurationTest extends AbstractIntegrationSpec {
         then:
         noExceptionThrown()
 
-        def project1Module = new MavenRepository(mvnRepo).module("org.gradle.test", "custom_project1", "0.1")
+        def project1Module = mavenRepo.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")
+        def project2Module = mavenRepo.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
index 4c7fccc..7b6e1d3 100644
--- 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
@@ -16,7 +16,6 @@
 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"() {
@@ -43,7 +42,7 @@ dependencies {
 uploadArchives {
     repositories {
         mavenDeployer {
-            repository(url: uri("maven-repo"))
+            repository(url: uri("${mavenRepo.uri}"))
         }
     }
 }
@@ -53,7 +52,7 @@ uploadArchives {
         run "uploadArchives"
 
         then:
-        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        def mavenModule = mavenRepo.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
index ac2b056..72b205c 100644
--- 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
@@ -19,9 +19,7 @@ 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.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.util.Resources
 import org.gradle.internal.SystemProperties
@@ -35,10 +33,7 @@ import org.junit.Test
 /**
  * @author Hans Dockter
  */
-class SamplesMavenPomGenerationIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-
+class SamplesMavenPomGenerationIntegrationTest extends AbstractIntegrationTest {
     private TestFile pomProjectDir
 
     @Rule public Resources resources = new Resources();
@@ -51,7 +46,7 @@ class SamplesMavenPomGenerationIntegrationTest {
     
     @Test
     void "can deploy to local repository"() {
-        def repo = new MavenRepository(pomProjectDir.file('pomRepo'))
+        def repo = maven(pomProjectDir.file('pomRepo'))
         def module = repo.module('deployGroup', 'mywar', '1.0MVN')
 
         executer.inDirectory(pomProjectDir).withTasks('uploadArchives').withArguments("--stacktrace").run()
@@ -65,7 +60,7 @@ class SamplesMavenPomGenerationIntegrationTest {
 
     @Test
     void "can install to local repository"() {
-        def repo = new MavenRepository(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
         def module = repo.module('installGroup', 'mywar', '1.0MVN')
         module.moduleDir.deleteDir()
 
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
index aa7dbee..df2c8a6 100755
--- 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
@@ -19,9 +19,7 @@ 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.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.util.Resources
 import org.gradle.internal.SystemProperties
@@ -33,9 +31,7 @@ import org.junit.Test
 /**
  * @author Hans Dockter
  */
-class SamplesMavenQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+class SamplesMavenQuickstartIntegrationTest extends AbstractIntegrationTest {
     @Rule public Resources resources = new Resources();
     @Rule public final Sample sample = new Sample('maven/quickstart')
 
@@ -50,7 +46,7 @@ class SamplesMavenQuickstartIntegrationTest {
     void "can publish to a local repository"() {
         executer.inDirectory(pomProjectDir).withTasks('uploadArchives').run()
 
-        def repo = new MavenRepository(pomProjectDir.file('pomRepo'))
+        def repo = maven(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)
@@ -59,7 +55,7 @@ class SamplesMavenQuickstartIntegrationTest {
 
     @Test
     void "can install to local repository"() {
-        def repo = new MavenRepository(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def repo = maven(new TestFile("$SystemProperties.userHome/.m2/repository"))
         def module = repo.module('gradle', 'quickstart', '1.0')
         module.moduleDir.deleteDir()
 
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
index dfaac85..4063311 100644
--- 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
@@ -17,23 +17,51 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenFileRepository
+import org.gradle.integtests.fixtures.MavenHttpRepository
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
 import org.junit.Rule
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.fixtures.IvyRepository
-import org.gradle.integtests.fixtures.MavenRepository
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
 
 abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
 
     def "setup"() {
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
         requireOwnUserHomeDir()
     }
 
-    IvyRepository ivyRepo(def dir = 'ivy-repo') {
-        return new IvyRepository(distribution.testFile(dir))
+    IvyFileRepository ivyRepo(def dir = 'ivy-repo') {
+        return ivy(dir)
+    }
+
+    IvyHttpRepository getIvyHttpRepo() {
+        return new IvyHttpRepository(server, "/repo", ivyRepo)
+    }
+
+    IvyHttpRepository ivyHttpRepo(String name) {
+        assert !name.startsWith("/")
+        return new IvyHttpRepository(server, "/${name}", ivyRepo(name))
+    }
+
+    MavenFileRepository mavenRepo(String name = "repo") {
+        return maven(name)
+    }
+
+    MavenHttpRepository getMavenHttpRepo() {
+        return new MavenHttpRepository(server, "/repo", mavenRepo)
+    }
+
+    MavenHttpRepository mavenHttpRepo(String name) {
+        assert !name.startsWith("/")
+        return new MavenHttpRepository(server, "/${name}", mavenRepo(name))
     }
 
-    MavenRepository mavenRepo() {
-        return new MavenRepository(file('repo'))
+    MavenHttpRepository mavenHttpRepo(String contextPath, MavenFileRepository backingRepo) {
+        return new MavenHttpRepository(server, contextPath, backingRepo)
     }
 }
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
index 60db2a9..c0357c4 100644
--- 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
@@ -15,15 +15,15 @@
  */
 package org.gradle.integtests.resolve
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 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
 
@@ -130,9 +130,9 @@ task listMissingClassifier << { configurations.missingClassifier.each { } }
     @Test
     @Issue("GRADLE-1342")
     public void resolutionDoesNotUseCachedArtifactFromDifferentRepository() {
-        def repo1 = new MavenRepository(testFile('repo1'))
+        def repo1 = maven('repo1')
         repo1.module('org.gradle.test', 'external1', '1.0').publish()
-        def repo2 = new MavenRepository(testFile('repo2'))
+        def repo2 = maven('repo2')
 
         testFile('settings.gradle') << 'include "a", "b"'
         testFile('build.gradle') << """
@@ -211,7 +211,7 @@ task test << {
 
         executer.withDeprecationChecksDisabled()
         def result = inTestDirectory().withTasks('test').run()
-        assert result.output.contains('relying on packaging to define the extension of the main artifact is deprecated')
+        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
     }
 
     @Test
@@ -351,7 +351,7 @@ task test << {
 """
         executer.withDeprecationChecksDisabled()
         def result = inTestDirectory().withTasks('test').run()
-        assert result.output.contains('relying on packaging to define the extension of the main artifact is deprecated')
+        assert result.output.contains('Relying on packaging to define the extension of the main artifact has been deprecated')
     }
 
     @Test
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
index 88e76fd..ecd40c8 100644
--- 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
@@ -15,11 +15,10 @@
  */
 package org.gradle.integtests.resolve
 
-import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 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.gradle.test.fixtures.server.http.HttpServer
 import org.junit.Rule
 
 class ArtifactOnlyResolutionIntegrationTest extends AbstractIntegrationSpec {
@@ -31,7 +30,7 @@ class ArtifactOnlyResolutionIntegrationTest extends AbstractIntegrationSpec {
     def "setup"() {
         requireOwnUserHomeDir()
 
-        projectA = repo().module('group', 'projectA').publish()
+        projectA = mavenRepo.module('group', 'projectA').publish()
         server.start()
 
         buildFile << """
@@ -88,8 +87,4 @@ task retrieve(type: Sync) {
         file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
         return result
     }
-
-    MavenRepository repo() {
-        return new MavenRepository(file('repo'))
-    }
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
index a7aa1a2..8e31aab 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
@@ -22,16 +22,14 @@ public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTes
         server.start()
 
         given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
 
         def cacheDir = distribution.userHomeDir.file('caches').toURI()
 
         and:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 configurations { compile }
 dependencies { compile 'group:projectA:1.2' }
@@ -44,7 +42,7 @@ task deleteCacheFiles(type: Delete) {
 """
 
         and:
-        server.allowGet("/repo", repo.rootDir)
+        module.allowAll()
 
         and:
         succeeds('listJars')
@@ -52,8 +50,8 @@ task deleteCacheFiles(type: Delete) {
         
         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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         then:
         succeeds('listJars')
@@ -62,9 +60,9 @@ task deleteCacheFiles(type: Delete) {
     public void "cache entries are segregated between different repositories"() {
         server.start()
         given:
-        def repo1 = ivyRepo('ivy-repo-a')
+        def repo1 = ivyHttpRepo('ivy-repo-a')
         def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
-        def repo2 = ivyRepo('ivy-repo-b')
+        def repo2 = ivyHttpRepo('ivy-repo-b')
         def module2 = repo2.module('org.gradle', 'testproject', '1.0').publishWithChangedContent()
 
         and:
@@ -84,26 +82,27 @@ subprojects {
 }
 project('a') {
     repositories {
-        ivy { url "http://localhost:${server.port}/repo-a" }
+        ivy { url "${repo1.uri}" }
     }
 }
 project('b') {
     repositories {
-        ivy { url "http://localhost:${server.port}/repo-b" }
+        ivy { url "${repo2.uri}" }
     }
+    retrieve.dependsOn(':a:retrieve')
 }
 """
 
         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)
+        module1.expectIvyGet()
+        module1.expectJarGet()
 
-        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)
+        module2.expectIvyHead()
+        module2.expectIvySha1Get()
+        module2.expectIvyGet()
+        module2.expectJarHead()
+        module2.expectJarSha1Get()
+        module2.expectJarGet()
 
         then:
         succeeds 'retrieve'
@@ -116,9 +115,9 @@ project('b') {
     public void "reuses a cached artifact retrieved from a different repository when sha1 matches"() {
         server.start()
         given:
-        def repo1 = ivyRepo('ivy-repo-a')
+        def repo1 = ivyHttpRepo('ivy-repo-a')
         def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
-        def repo2 = ivyRepo('ivy-repo-b')
+        def repo2 = ivyHttpRepo('ivy-repo-b')
         def module2 = repo2.module('org.gradle', 'testproject', '1.0').publish()
 
         and:
@@ -138,24 +137,25 @@ subprojects {
 }
 project('a') {
     repositories {
-        ivy { url "http://localhost:${server.port}/repo-a" }
+        ivy { url "${repo1.uri}" }
     }
 }
 project('b') {
     repositories {
-        ivy { url "http://localhost:${server.port}/repo-b" }
+        ivy { url "${repo2.uri}" }
     }
+    retrieve.dependsOn(':a:retrieve')
 }
 """
 
         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)
+        module1.expectIvyGet()
+        module1.expectJarGet()
 
-        module2.expectIvyHead(server, "/repo-b")
-        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml.sha1', module2.sha1File(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))
+        module2.expectIvyHead()
+        module2.expectIvySha1Get()
+        module2.expectJarHead()
+        module2.expectJarSha1Get()
 
         then:
         succeeds 'retrieve'
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
index 3529728..7877a8a 100644
--- 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
@@ -16,13 +16,12 @@
 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()
+        mavenRepo.module("org.other", "externalA", 1.2).publish()
+        mavenRepo.module("org.other", "externalB", 2.1).publish()
 
         and:
         file('settings.gradle') << "include 'a', 'b'"
@@ -30,7 +29,7 @@ class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
         and:
         buildFile << """
 allprojects {
-    repositories { maven { url '$repo.uri' } }
+    repositories { maven { url '$mavenRepo.uri' } }
 }
 project(":a") {
     configurations {
@@ -63,7 +62,7 @@ project(":b") {
 
     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()
+        mavenRepo.module("org.other", "externalA", 1.2).publish()
 
         and:
         file('settings.gradle') << "include 'a', 'b'"
@@ -71,7 +70,7 @@ project(":b") {
         and:
         buildFile << """
 allprojects {
-    repositories { maven { url '$repo.uri' } }
+    repositories { maven { url '$mavenRepo.uri' } }
 }
 project(":a") {
     configurations {
@@ -134,7 +133,7 @@ project(":b") {
 
     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()
+        mavenRepo.module("group", "externalA", 1.5).publish()
 
         and:
         file('settings.gradle') << "include 'a', 'b'"
@@ -143,7 +142,7 @@ project(":b") {
         buildFile << """
 allprojects {
     apply plugin: 'base'
-    repositories { maven { url '${repo.uri}' } }
+    repositories { maven { url '${mavenRepo.uri}' } }
 }
 
 project(":a") {
@@ -171,7 +170,7 @@ project(":b") {
 
     public void "non-transitive project dependency includes only the artifacts of the target configuration"() {
         given:
-        repo.module("group", "externalA", 1.5).publish()
+        mavenRepo.module("group", "externalA", 1.5).publish()
 
         and:
         file('settings.gradle') << "include 'a', 'b'"
@@ -296,8 +295,4 @@ project('c') {
         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/ResolvedConfigurationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
index 7ba320a..8c1b112 100644
--- 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
@@ -20,30 +20,27 @@ import org.gradle.api.artifacts.LenientConfiguration
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.specs.Specs
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.MavenRepository
 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 }
+            maven { url mavenRepo.uri }
         }
     }
 
     @Test
     public void "resolves leniently"() {
-        maven(repo).module('org.foo', 'hiphop').publish()
-        maven(repo).module('org.foo', 'rock').dependsOn("some unresolved dependency").publish()
+        mavenRepo.module('org.foo', 'hiphop').publish()
+        mavenRepo.module('org.foo', 'rock').dependsOn("some unresolved dependency").publish()
 
         project.dependencies {
             compile 'org.foo:hiphop:1.0'
@@ -68,19 +65,15 @@ public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTes
         assert unresolved.find { it.selector.name == '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()
+        mavenRepo.module('org.foo', 'hiphop').publish()
+        mavenRepo.module('org.foo', 'rock').dependsOn("some unresolved dependency").publish()
 
         project.allprojects { apply plugin: 'java' }
 
         project.repositories {
-            maven { url repo }
+            maven { url mavenRepo }
         }
 
         project.configurations {
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
index 820824c..237bc0a 100644
--- 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
@@ -17,12 +17,15 @@ package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.junit.Test
+import spock.lang.Issue
+
 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()
@@ -634,7 +637,6 @@ project(':tool') {
         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")
@@ -685,6 +687,128 @@ task checkDeps << {
         executer.withTasks("checkDeps").withArguments('-s').run()
     }
 
+    @Test
+    void "previously evicted nodes should contain correct target version"() {
+        /*
+        a1->b1
+        a2->b2->a1
+
+        resolution process:
+
+        1. stop resolution, resolve conflict a1 vs a2
+        2. select a2, restart resolution
+        3. stop, resolve b1 vs b2
+        4. select b2, restart
+        5. resolve b2 dependencies, a1 has been evicted previously but it should show correctly on the report
+           ('dependencies' report pre 1.2 would not show the a1 dependency leaf for this scenario)
+        */
+
+        ivyRepo.module("org", "b", '1.0').publish()
+        ivyRepo.module("org", "a", '1.0').dependsOn("org", "b", '1.0').publish()
+        ivyRepo.module("org", "b", '2.0').dependsOn("org", "a", "1.0").publish()
+        ivyRepo.module("org", "a", '2.0').dependsOn("org", "b", '2.0').publish()
+
+        file("build.gradle") << """
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+            }
+
+            configurations {
+                conf
+            }
+            dependencies {
+                conf 'org:a:1.0', 'org:a:2.0'
+            }
+            task checkDeps << {
+                assert configurations.conf*.name == ['a-2.0.jar', 'b-2.0.jar']
+                def result = configurations.conf.incoming.resolutionResult
+                assert result.allModuleVersions.size() == 3
+                def root = result.root
+                assert root.dependencies*.toString() == ['org:a:1.0 -> 2.0', 'org:a:2.0']
+                def a = result.allModuleVersions.find { it.id.name == 'a' }
+                assert a.dependencies*.toString() == ['org:b:2.0']
+                def b = result.allModuleVersions.find { it.id.name == 'b' }
+                assert b.dependencies*.toString() == ['org:a:1.0 -> 2.0']
+            }
+        """
+
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    @Issue("GRADLE-2555")
+    void "can deal with transitive with parent in conflict"() {
+        /*
+            Graph looks like…
+
+            \--- org:a:1.0
+                 \--- org:in-conflict:1.0 -> 2.0
+                      \--- org:target:1.0
+                           \--- org:target-child:1.0
+            \--- org:b:1.0
+                 \--- org:b-child:1.0
+                      \--- org:in-conflict:2.0 (*)
+
+            This is the simplest structure I could boil it down to that produces the error.
+            - target *must* have a child
+            - Having "b" depend directly on "in-conflict" does not produce the error, needs to go through "b-child"
+         */
+
+        mavenRepo.module("org", "target-child", "1.0").
+                publish()
+
+        mavenRepo.module("org", "target", "1.0").
+                dependsOn("org", "target-child", "1.0").
+                publish()
+
+        mavenRepo.module("org", "in-conflict", "1.0").
+                dependsOn("org", "target", "1.0").
+                publish()
+
+        mavenRepo.module("org", "in-conflict", "2.0").
+                dependsOn("org", "target", "1.0").
+                publish()
+
+        mavenRepo.module("org", "a", '1.0').
+                dependsOn("org", "in-conflict", "1.0").
+                publish()
+
+        mavenRepo.module("org", "b-child", '1.0').
+                dependsOn("org", "in-conflict", "2.0").
+                publish()
+
+        mavenRepo.module("org", "b", '1.0').
+                dependsOn("org", "b-child", "1.0").
+                publish()
+
+        when:
+        file("build.gradle") << """
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+            }
+
+            configurations { conf }
+
+            dependencies {
+                conf "org:a:1.0", "org:b:1.0"
+            }
+
+        task checkDeps << {
+            assert configurations.conf*.name == ['a-1.0.jar', 'b-1.0.jar', 'target-child-1.0.jar', 'target-1.0.jar', 'in-conflict-2.0.jar', 'b-child-1.0.jar']
+            def result = configurations.conf.incoming.resolutionResult
+            assert result.allModuleVersions.size() == 7
+            def a = result.allModuleVersions.find { it.id.name == 'a' }
+            assert a.dependencies*.toString() == ['org:in-conflict:1.0 -> 2.0']
+            def bChild = result.allModuleVersions.find { it.id.name == 'b-child' }
+            assert bChild.dependencies*.toString() == ['org:in-conflict:2.0']
+            def target = result.allModuleVersions.find { it.id.name == 'target' }
+            assert target.dependents*.from*.toString() == ['org:in-conflict:2.0']
+        }
+        """
+
+        executer.withTasks("checkDeps").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
index 09defaa..5ad6ae5 100644
--- 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
@@ -15,44 +15,59 @@
  */
 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 mavenRepo1 = mavenHttpRepo("maven1")
+    def mavenRepo2 = mavenHttpRepo("maven2")
+    def ivyRepo1 = ivyHttpRepo("ivy1")
+    def ivyRepo2 = ivyHttpRepo("ivy2")
 
     def "setup"() {
-        init()
-        projectB = mavenRepo().module('org.name', 'projectB').publish()
-        ivyProjectB = ivyRepo().module('org.name', 'projectB').publish()
+        server.start()
+
+        buildFile << """
+            repositories {
+                if (project.hasProperty('mavenRepository1')) {
+                    maven { url '${mavenRepo1.uri}' }
+                } else if (project.hasProperty('mavenRepository2')) {
+                    maven { url '${mavenRepo2.uri}' }
+                } else if (project.hasProperty('ivyRepository1')) {
+                    ivy { url '${ivyRepo1.uri}' }
+                } else if (project.hasProperty('ivyRepository2')) {
+                    ivy { url '${ivyRepo2.uri}' }
+                } 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 "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)
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectPomGet()
+        projectBRepo1.expectArtifactGet()
 
         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))
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectPomHead()
+        projectBRepo2.expectPomSha1Get()
+        projectBRepo2.expectArtifactHead()
+        projectBRepo2.expectArtifactSha1Get()
 
         then:
         succeedsWith 'mavenRepository2'
@@ -60,17 +75,19 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
     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)
+        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectIvyGet()
+        projectBRepo1.expectJarGet()
 
         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))
+        def projectBRepo2 = ivyRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectIvyHead()
+        projectBRepo2.expectIvySha1Get()
+        projectBRepo2.expectJarHead()
+        projectBRepo2.expectJarSha1Get()
 
         then:
         succeedsWith 'ivyRepository2'
@@ -78,16 +95,18 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
     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)
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectPomGet()
+        projectBRepo1.expectArtifactGet()
 
         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))
+        def projectBRepo2 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectIvyGet()
+        projectBRepo2.expectJarHead()
+        projectBRepo2.expectJarSha1Get()
 
         then:
         succeedsWith 'ivyRepository1'
@@ -95,16 +114,18 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
     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)
+        def projectBRepo1 = ivyRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectIvyGet()
+        projectBRepo1.expectJarGet()
 
         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))
+        def projectBRepo2 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectPomGet()
+        projectBRepo2.expectArtifactHead()
+        projectBRepo2.expectArtifactSha1Get()
 
         then:
         succeedsWith 'mavenRepository1'
@@ -116,8 +137,11 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
         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))
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectPomHead()
+        projectBRepo2.expectPomSha1Get()
+        projectBRepo2.expectArtifactHead()
+        projectBRepo2.expectArtifactSha1Get()
 
         then:
         succeedsWith 'mavenRepository2'
@@ -125,19 +149,21 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
     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)
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectPomGet()
+        projectBRepo1.expectArtifactGet()
 
         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)
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo2.expectPomHead()
+        projectBRepo2.expectPomSha1GetMissing()
+        projectBRepo2.expectPomGet()
+        projectBRepo2.expectArtifactHead()
+        projectBRepo2.expectArtifactSha1GetMissing()
+        projectBRepo2.expectArtifactGet()
 
         then:
         succeedsWith 'mavenRepository2'
@@ -145,53 +171,26 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
 
     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)
+        def projectBRepo1 = mavenRepo1.module('org.name', 'projectB', '1.0').publish()
+        projectBRepo1.expectPomGet()
+        projectBRepo1.expectArtifactGet()
 
         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)
+        def projectBRepo2 = mavenRepo2.module('org.name', 'projectB', '1.0').publishWithChangedContent()
+        projectBRepo2.expectPomHead()
+        projectBRepo2.expectPomSha1Get()
+        projectBRepo2.expectPomGet()
+        projectBRepo2.expectArtifactHead()
+        projectBRepo2.expectArtifactSha1Get()
+        projectBRepo2.expectArtifactGet()
 
         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'
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
index 6fc6cf8..da91b51 100644
--- 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
@@ -16,24 +16,25 @@
 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.MavenFileRepository
+import org.gradle.integtests.fixtures.MavenHttpRepository
 import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
 
 @TargetVersions('1.0-milestone-6+')
 class CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
+    final MavenHttpRepository httpRepo = new MavenHttpRepository(server, new MavenFileRepository(file("maven-repo")))
 
     def "uses cached artifacts from previous Gradle version when no sha1 header"() {
         given:
-        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
         server.sendSha1Header = false
         server.start()
         buildFile << """
 repositories {
-    maven { url 'http://localhost:${server.port}' }
+    maven { url '${httpRepo.uri}' }
 }
 configurations { compile }
 dependencies {
@@ -49,10 +50,10 @@ task retrieve(type: Sync) {
         def userHome = file('user-home')
 
         when:
-        server.allowGet('/org', file('repo/org'))
+        projectB.allowAll()
 
         and:
-        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
 
         then:
         file('libs').assertHasDescendants('projectB-1.0.jar')
@@ -60,13 +61,13 @@ task retrieve(type: Sync) {
 
         when:
         server.resetExpectations()
-        projectB.allowPomHead(server)
-        projectB.allowPomSha1Get(server)
-        projectB.allowArtifactHead(server)
-        projectB.allowArtifactSha1Get(server)
+        projectB.allowPomHead()
+        projectB.allowPomSha1Get()
+        projectB.allowArtifactHead()
+        projectB.allowArtifactSha1Get()
 
         and:
-        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
 
         then:
         file('libs').assertHasDescendants('projectB-1.0.jar')
@@ -75,12 +76,12 @@ task retrieve(type: Sync) {
 
     def "uses cached artifacts from previous Gradle version with sha1 header"() {
         given:
-        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+        def projectB = httpRepo.module('org.name', 'projectB', '1.0').publish()
         server.sendSha1Header = true
         server.start()
         buildFile << """
 repositories {
-    maven { url 'http://localhost:${server.port}' }
+    maven { url '${httpRepo.uri}' }
 }
 configurations { compile }
 dependencies {
@@ -96,10 +97,10 @@ task retrieve(type: Sync) {
         def userHome = file('user-home')
 
         when:
-        server.allowGet('/org', file('repo/org'))
+        projectB.allowAll()
 
         and:
-        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
 
         then:
         file('libs').assertHasDescendants('projectB-1.0.jar')
@@ -107,14 +108,63 @@ task retrieve(type: Sync) {
 
         when:
         server.resetExpectations()
-        projectB.allowPomHead(server)
-        projectB.allowArtifactHead(server)
+        projectB.allowPomHead()
+        projectB.allowArtifactHead()
 
         and:
-        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version current withGradleUserHomeDir 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 that match dynamic version"() {
+        given:
+        def projectB = httpRepo.module('org.name', 'projectB', '1.1').publish()
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url '${httpRepo.uri}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:[1.0,2.0]'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        httpRepo.expectMetaDataGet("org.name", "projectB")
+        projectB.allowAll()
+
+        and:
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.1.jar')
+        def snapshot = file('libs/projectB-1.1.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        httpRepo.allowMetaDataGet("org.name", "projectB")
+        projectB.allowPomHead()
+        projectB.allowPomSha1Get()
+        projectB.allowArtifactHead()
+        projectB.allowArtifactSha1Get()
+
+        and:
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.1.jar')
+        file('libs/projectB-1.1.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
 }
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
index 7900dc6..87433ec 100644
--- 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
@@ -15,25 +15,26 @@
  */
 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.gradle.integtests.fixtures.MavenHttpRepository
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.server.http.HttpServer
 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.
 @TargetVersions('1.0-milestone-3')
 class M3CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
+    final MavenHttpRepository remoteRepo = new MavenHttpRepository(server, mavenRepo)
 
     def "uses cached artifacts from previous Gradle version"() {
         given:
-        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+        def projectB = remoteRepo.module('org.name', 'projectB').publish()
 
         server.start()
         buildFile << """
 repositories {
-    mavenRepo(urls: ['http://localhost:${server.port}'])
+    mavenRepo(urls: ['${remoteRepo.uri}'])
 }
 configurations { compile }
 dependencies {
@@ -49,10 +50,10 @@ task retrieve(type: Sync) {
         def userHome = file('user-home')
 
         when:
-        server.allowGet("/org", file('repo/org'));
+        projectB.allowAll()
 
         and:
-        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version previous withGradleUserHomeDir userHome withTasks 'retrieve' run()
 
         then:
         file('libs').assertHasDescendants('projectB-1.0.jar')
@@ -60,12 +61,12 @@ task retrieve(type: Sync) {
 
         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))
+        projectB.expectPomGet()
+        projectB.expectArtifactHead()
+        projectB.expectArtifactSha1Get()
 
         and:
-        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+        version current withGradleUserHomeDir userHome withTasks 'retrieve' run()
 
         then:
         file('libs').assertHasDescendants('projectB-1.0.jar')
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
index aedf714..d738a3e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
@@ -19,19 +19,16 @@ import org.gradle.integtests.fixture.M2Installation
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class MavenM2CacheReuseIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def setup(){
-        requireOwnUserHomeDir();
-    }
-
     def "uses cached artifacts from maven local cache"() {
         given:
-        publishAndInstallToMaven()
+        def module1 = mavenHttpRepo.module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
+        def m2 = new M2Installation(testDir).generateGlobalSettingsFile()
+        def module2 = m2.mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
         server.start()
 
         buildFile.text = """
 repositories {
-    maven { url "http://localhost:${server.port}" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 configurations { compile }
 dependencies {
@@ -42,23 +39,17 @@ task retrieve(type: Sync) {
     into 'build'
 }
 """
+        and:
+        module1.expectPomHead()
+        module1.expectPomSha1Get()
+        module1.expectArtifactHead()
+        module1.expectArtifactSha1Get()
 
         when:
-        def repoFile = file('repo')
-        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:
-        executer.withArguments("-Duser.home=${distribution.getUserHomeDir()}")
+        executer.withEnvironmentVars(M2_HOME: m2.globalMavenDirectory)
         run 'retrieve'
-    }
 
-    private def publishAndInstallToMaven() {
-        def module1 = mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0")
-        module1.publish();
-        def module2 = new M2Installation(distribution.getUserHomeDir().file(".m2")).mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0")
-        module2.publish()
+        then:
+        file('build/foo-1.0.jar').assertIsCopyOf(module2.artifactFile)
     }
 }
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
index b0295cb..5be3148 100644
--- 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
@@ -16,23 +16,18 @@
 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"() {
+    public void "will refresh non-changing module when run with --refresh-dependencies"() {
         given:
         server.start()
-        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
 
         and:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 configurations { compile }
 dependencies { compile 'org.name:projectA:1.2' }
@@ -42,7 +37,7 @@ task retrieve(type: Sync) {
 }
 """
         and:
-        server.allowGet('/repo', mavenRepo().rootDir)
+        module.allowAll()
 
         when:
         succeeds 'retrieve'
@@ -66,12 +61,12 @@ task retrieve(type: Sync) {
         server.start()
 
         given:
-        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+        def module = mavenHttpRepo.module('org.name', 'projectA', '1.2').publish()
 
         buildFile << """
 repositories {
     maven {
-        url "http://localhost:${server.port}"
+        url "${mavenHttpRepo.uri}"
     }
 }
 configurations { missing }
@@ -82,16 +77,16 @@ 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')
+        module.expectPomGetMissing()
+        module.expectArtifactHeadMissing()
 
         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)
+        module.expectPomGet()
+        module.expectArtifactGet()
 
         then:
         executer.withArguments("--refresh-dependencies")
@@ -105,7 +100,7 @@ task showMissing << { println configurations.missing.files }
         buildFile << """
 repositories {
     maven {
-        url "http://localhost:${server.port}"
+        url "${mavenHttpRepo.uri}"
     }
 }
 configurations { compile }
@@ -119,19 +114,19 @@ task retrieve(type: Sync) {
 """
 
         and:
-        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+        def module = mavenHttpRepo.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')
+        module.expectPomGet()
+        module.expectArtifactGetMissing()
 
         then:
         fails "retrieve"
 
         when:
         server.resetExpectations()
-        module.expectPomHead(server)
-        module.expectArtifactGet(server)
+        module.expectPomHead()
+        module.expectArtifactGet()
 
         then:
         executer.withArguments("--refresh-dependencies")
@@ -143,12 +138,12 @@ task retrieve(type: Sync) {
 
         given:
         server.start()
-        def module = mavenRepo().module("org.name", "unique", "1.0-SNAPSHOT").publish()
+        def module = mavenHttpRepo.module("org.name", "unique", "1.0-SNAPSHOT").publish()
 
         and:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 configurations { compile }
 configurations.all {
@@ -164,7 +159,7 @@ task retrieve(type: Sync) {
 """
 
         when:  "Server handles requests"
-        server.allowGet("/repo", mavenRepo().rootDir)
+        module.allowAll()
 
         and: "We resolve dependencies"
         run 'retrieve'
@@ -193,7 +188,7 @@ task retrieve(type: Sync) {
         and:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 configurations { compile }
 dependencies { compile 'org.name:projectA:1.2' }
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
index 25126a7..1916ac0 100644
--- 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
@@ -16,17 +16,17 @@
 
 package org.gradle.integtests.resolve.caching
 
-import org.gradle.integtests.fixtures.IvyModule
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.ivy.IvyHttpModule
+import org.gradle.test.fixtures.server.http.HttpServer
 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
+    IvyHttpModule module
 
     TestFile downloaded
     TestFile.Snapshot lastState
@@ -35,7 +35,7 @@ class CachedDependencyResolutionIntegrationTest extends AbstractDependencyResolu
         server.start()
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -48,21 +48,20 @@ dependencies {
     compile group: "group", name: "projectA", version: "1.1", changing: true
 }
 
-task retrieve(type: Copy) {
+task retrieve(type: Sync) {
     into 'build'
     from configurations.compile
 }
 """
 
-        module = ivyRepo().module("group", "projectA", "1.1")
-        module.publish()
+        module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
         downloaded = file('build/projectA-1.1.jar')
     }
 
     void initialResolve() {
-        module.expectIvyGet(server)
-        module.expectArtifactGet(server)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         resolve()
     }
@@ -76,47 +75,47 @@ task retrieve(type: Copy) {
     }
 
     void headOnlyRequests() {
-        module.expectIvyHead(server)
-        module.expectArtifactHead(server)
+        module.expectIvyHead()
+        module.expectJarHead()
     }
 
     void headSha1ThenGetRequests() {
-        module.expectIvyHead(server)
-        module.expectIvySha1Get(server)
-        module.expectIvyGet(server)
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
 
-        module.expectArtifactHead(server)
-        module.expectArtifactSha1Get(server)
-        module.expectArtifactGet(server)
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
     }
 
     void sha1OnlyRequests() {
-        module.expectIvySha1Get(server)
-        module.expectArtifactSha1Get(server)
+        module.expectIvySha1Get()
+        module.expectJarSha1Get()
     }
 
     void sha1ThenGetRequests() {
-        module.expectIvySha1Get(server)
-        module.expectIvyGet(server)
+        module.expectIvySha1Get()
+        module.expectIvyGet()
 
-        module.expectArtifactSha1Get(server)
-        module.expectArtifactGet(server)
+        module.expectJarSha1Get()
+        module.expectJarGet()
     }
 
     void headThenSha1Requests() {
-        module.expectIvyHead(server)
-        module.expectIvySha1Get(server)
+        module.expectIvyHead()
+        module.expectIvySha1Get()
 
-        module.expectArtifactHead(server)
-        module.expectArtifactSha1Get(server)
+        module.expectJarHead()
+        module.expectJarSha1Get()
     }
 
     void headThenGetRequests() {
-        module.expectIvyHead(server)
-        module.expectIvyGet(server)
+        module.expectIvyHead()
+        module.expectIvyGet()
 
-        module.expectArtifactHead(server)
-        module.expectArtifactGet(server)
+        module.expectJarHead()
+        module.expectJarGet()
     }
 
     void unchangedResolve() {
@@ -219,6 +218,4 @@ task retrieve(type: Copy) {
         headThenGetRequests()
         changedResolve()
     }
-
-
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
new file mode 100644
index 0000000..56878b0
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedMissingModulesIntegrationTest.groovy
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.resolve.AbstractDependencyResolutionTest
+import spock.lang.Ignore
+import spock.lang.IgnoreIf
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+
+class CachedMissingModulesIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "cached not-found information for dynamic version is ignored if module is not available in any repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        buildFile << """
+            repositories {
+                maven {
+                    name 'repo1'
+                    url '${repo1.uri}'
+                }
+                maven {
+                    name 'repo2'
+                    url '${repo2.uri}'
+                }
+            }
+            configurations { compile }
+            dependencies {
+                compile 'group:projectA:latest.integration'
+            }
+
+            task retrieve(type: Sync) {
+                into 'libs'
+                from configurations.compile
+            }
+            """
+
+        when:
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo2.expectMetaDataGetMissing("group", "projectA")
+        repo2.expectMetaDataGetMissing("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+
+        then:
+        runAndFail 'retrieve'
+
+        when:
+        server.resetExpectations()
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectMetaDataGetMissing("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo1.expectDirectoryListGet("group", "projectA")
+        repo2Module.publish()
+        repo2.expectMetaDataGet("group", "projectA")
+        repo2Module.expectPomGet()
+        repo2Module.expectArtifactGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        run 'retrieve'
+    }
+
+    def "cached not-found information for fixed version is ignored if module is not available in any repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        buildFile << """
+    repositories {
+        maven {
+            name 'repo1'
+            url '${repo1.uri}'
+        }
+        maven {
+            name 'repo2'
+            url '${repo2.uri}'
+        }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'group:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        repo1Module.expectPomGetMissing()
+        repo1Module.expectArtifactHeadMissing()
+        repo2Module.expectPomGetMissing()
+        repo2Module.expectArtifactHeadMissing()
+
+        then:
+        runAndFail 'retrieve'
+
+        when:
+        server.resetExpectations()
+        repo1Module.expectPomGetMissing()
+        repo1Module.expectArtifactHeadMissing()
+        repo2Module.publish()
+        repo2Module.expectPomGet()
+        repo2Module.expectArtifactGet()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        run 'retrieve'
+    }
+
+    @IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter.executeParallel })
+    def "hit each remote repo only once per build and missing module"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenHttpRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        settingsFile << "include 'subproject'"
+        buildFile << """
+            allprojects{
+                repositories {
+                    maven {
+                        name 'repo1'
+                        url '${repo1.uri}'
+                    }
+                    maven {
+                        name 'repo2'
+                        url '${repo2.uri}'
+                    }
+                }
+            }
+            configurations {
+                config1
+            }
+            dependencies {
+                config1 'group:projectA:1.0'
+            }
+
+            task resolveConfig1 << {
+                   configurations.config1.incoming.resolutionResult.allDependencies{
+                        it instanceof UnresolvedDependencyResult
+                   }
+            }
+
+            project(":subproject"){
+                configurations{
+                    config2
+                }
+                dependencies{
+                    config2 'group:projectA:1.0'
+                }
+                task resolveConfig2 << {
+                    configurations.config2.incoming.resolutionResult.allDependencies{
+                        it instanceof UnresolvedDependencyResult
+                    }
+                }
+            }
+        """
+        when:
+        repo1Module.expectPomGetMissing()
+        repo1Module.expectArtifactHeadMissing()
+        repo2Module.expectPomGetMissing()
+        repo2Module.expectArtifactHeadMissing()
+
+        then:
+        run('resolveConfig1')
+
+        when:
+        server.resetExpectations()
+        repo1Module.expectPomGetMissing()
+        repo1Module.expectArtifactHeadMissing()
+        repo2Module.expectPomGetMissing()
+        repo2Module.expectArtifactHeadMissing()
+
+        then:
+        run "resolveConfig1", "resolveConfig2"
+    }
+
+    def "does not hit remote repositories if version is available in local repo"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo1Module = repo1.module("group", "projectA", "1.0")
+        def repo2 = mavenRepo("repo2")
+        def repo2Module = repo2.module("group", "projectA", "1.0")
+
+        buildFile << """
+        repositories {
+           maven {
+               name 'repo1'
+               url '${repo1.uri}'
+           }
+           maven {
+               name 'repo2'
+               url '${repo2.uri}'
+           }
+       }
+       configurations { compile }
+       dependencies {
+           compile 'group:projectA:1.0'
+       }
+
+       task retrieve(type: Sync) {
+           into 'libs'
+           from configurations.compile
+       }
+       """
+
+        when:
+        repo2Module.publish()
+        repo1Module.expectPomGetMissing()
+        repo1Module.expectArtifactHeadMissing()
+
+        then:
+        run 'retrieve'
+
+        when:
+        server.resetExpectations()
+        then:
+        run 'retrieve'
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.groovy
new file mode 100644
index 0000000..1a8b161
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FileSystemResolverIntegrationTest.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.resolve.custom
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class FileSystemResolverIntegrationTest extends AbstractIntegrationSpec {
+
+    def "file system resolvers use item at source by default"() {
+        when:
+        def module = ivyRepo.module("group", "projectA", "1.2")
+        module.publish()
+        def jar = module.jarFile
+        jar.text = "1"
+        
+        buildFile << """
+            def repoDir = file('${ivyRepo.uri}')
+            repositories {
+                add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+                    name = "repo"
+                    addIvyPattern(repoDir.absolutePath + "/[organization]/[module]/[revision]/ivy-[module]-[revision].xml")
+                    addArtifactPattern(repoDir.absolutePath + "/[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/FilerSystemResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FilerSystemResolverIntegrationTest.groovy
deleted file mode 100644
index 947ebff..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FilerSystemResolverIntegrationTest.groovy
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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
index 39a14a2..845b8fa 100644
--- 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
@@ -16,8 +16,9 @@
 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.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyRepository
+import org.gradle.test.fixtures.server.sftp.SFTPServer
 import org.junit.Rule
 
 class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
@@ -25,6 +26,8 @@ class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
     @Rule
     public final SFTPServer server = new SFTPServer(distribution.temporaryFolder)
 
+    @Rule ProgressLoggingFixture progressLogging
+
     def "setup"() {
         requireOwnUserHomeDir()
     }
@@ -55,14 +58,15 @@ task listJars << {
 }
 """
         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
+                "repos/libs/group/projectA/1.2/projectA-1.2.jar"
+        ] as Set
+
+        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/ivy-1.2.xml")
+        progressLogging.downloadProgressLogged("repos/libs/group/projectA/1.2/projectA-1.2.jar")
 
         when:
         server.clearRequests()
@@ -73,6 +77,6 @@ task listJars << {
     }
 
     IvyRepository ivyRepo() {
-            return new IvyRepository(server.file("repos/libs/"))
+        return ivy(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
index a22a21b..c0eb839 100644
--- 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
@@ -15,46 +15,55 @@
  */
 package org.gradle.integtests.resolve.custom
 
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.junit.Rule
 
 class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
 
+    @Rule ProgressLoggingFixture progressLogging
+
+    def setup() {
+        server.expectUserAgent(null) // custom resolver uses apache/ivy as useragent strings
+    }
+
     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()
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2').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]")
+        addIvyPattern("${ivyHttpRepo.ivyPattern}")
+        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
     }
 }
 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)
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
 
         then:
         succeeds 'listJars'
 
+        and:
+        progressLogging.downloadProgressLogged(module.ivyFileUri)
+        progressLogging.downloadProgressLogged(module.jarFileUri)
+
         when:
         server.resetExpectations()
-        // No extra calls for cached dependencies
 
+        // No extra calls for cached dependencies
         then:
         succeeds 'listJars'
     }
@@ -62,17 +71,15 @@ task 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()
+        def module = ivyHttpRepo.module('group', 'projectA', '1.2-SNAPSHOT').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]")
+        addIvyPattern("${ivyHttpRepo.ivyPattern}")
+        addArtifactPattern("${ivyHttpRepo.artifactPattern}")
         changingMatcher = 'regexp'
         changingPattern = '.*SNAPSHOT.*'
     }
@@ -86,10 +93,10 @@ task retrieve(type: Sync) {
 }
 """
         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)
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
 
         run 'retrieve'
 
@@ -103,10 +110,10 @@ task retrieve(type: Sync) {
 
         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)
+        module.expectIvyHead()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarGet()
 
         run 'retrieve'
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..c43372a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.http
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+import static org.gradle.util.Matchers.containsText
+
+abstract class AbstractHttpsRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule TestResources resources
+    File clientStore // contains the client's public and private keys
+    File serverStore // contains the server's public and private keys
+
+    abstract protected String setupRepo()
+
+    def "resolve with server certificate"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withTasks('libs').run()
+
+        then:
+        file('libs').assertHasDescendants('my-module-1.0.jar')
+    }
+
+    def "resolve with server and client certificate"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh", clientStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        executer.withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
+                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
+                .withTasks('libs').run()
+
+        then:
+        file('libs').assertHasDescendants('my-module-1.0.jar')
+    }
+
+    def "decent error message when client can't authenticate server"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh")
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
+                .withArgument("-Djavax.net.ssl.trustStore=$clientStore.path") // intentionally use wrong trust store for client
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withTasks('libs').runWithFailure()
+
+        then:
+        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
+        failure.assertHasCause("peer not authenticated")
+    }
+
+    def "decent error message when server can't authenticate client"() {
+        setupCertStores()
+        server.enableSsl(serverStore.path, "asdfgh", serverStore.path, "asdfgh") // intentionally use wrong trust store for server
+        server.start()
+
+        def repoType = setupRepo()
+        setupBuildFile(repoType)
+
+        when:
+        def failure = executer.withStackTraceChecksDisabled() // Jetty logs stuff to console
+                .withArgument("-Djavax.net.ssl.trustStore=$serverStore.path")
+                .withArgument("-Djavax.net.ssl.trustStorePassword=asdfgh")
+                .withArgument("-Djavax.net.ssl.keyStore=$clientStore.path")
+                .withArgument("-Djavax.net.ssl.keyStorePassword=asdfgh")
+                .withTasks('libs').runWithFailure()
+
+        then:
+        failure.assertThatCause(containsText("Could not GET 'https://localhost:(\\d*)/repo1/my-group/my-module/1.0/"))
+        failure.assertHasCause("peer not authenticated")
+    }
+
+    def setupCertStores() {
+        clientStore = resources.dir.file("clientStore")
+        serverStore = resources.dir.file("serverStore")
+    }
+
+    private void setupBuildFile(String repoType) {
+        buildFile << """
+repositories {
+    $repoType { url 'https://localhost:${server.sslPort}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'my-group:my-module:1.0'
+}
+task libs(type: Copy) {
+    into 'libs'
+    from configurations.compile
+}
+        """
+    }
+}
+
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
index a58575f..76023f1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
@@ -15,10 +15,10 @@
  */
 package org.gradle.integtests.resolve.http
 
-import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
 import org.hamcrest.Matchers
 import spock.lang.Unroll
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class HttpAuthenticationDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
     static String badCredentials = "credentials{username 'testuser'; password 'bad'}"
@@ -117,12 +117,12 @@ task listJars << {
         server.expectGet('/repo/group/projectB/2.3/projectB-2.3.pom', 'username', 'password', moduleB.pomFile)
         server.expectGet('/repo/group/projectB/2.3/projectB-2.3.jar', 'username', 'password', moduleB.artifactFile)
 
-        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile) // TODO: double check why we have two get requests here.
+        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
         server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
         server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.pom", 'username', 'password', moduleC.pomFile)
         server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.jar", 'username', 'password', moduleC.artifactFile)
 
-        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)  //same here: two requests necessary?
+        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
         server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
         server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.pom", 'username', 'password', moduleD.pomFile)
         server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.jar", 'username', 'password', moduleD.artifactFile)
@@ -159,7 +159,7 @@ task listJars << {
 
         and:
         server.authenticationScheme = authScheme
-        server.allowGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
+        server.allowGetOrHead('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
 
         then:
         fails 'listJars'
@@ -203,7 +203,7 @@ task listJars << {
 
         and:
         server.authenticationScheme = authScheme
-        server.allowGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
+        server.allowGetOrHead('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
 
         then:
         fails 'listJars'
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
index 372f922..be4bc28 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
@@ -15,12 +15,12 @@
  */
 package org.gradle.integtests.resolve.http
 
-import org.gradle.integtests.fixtures.TestProxyServer
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.test.fixtures.server.http.TestProxyServer
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Unroll
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class HttpProxyResolveIntegrationTest extends AbstractDependencyResolutionTest {
     @Rule TestProxyServer proxyServer = new TestProxyServer(server)
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
index 04ed3d2..f4f17d9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.integtests.resolve.http
 
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.server.http.HttpServer
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Issue
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.integtests.fixtures.HttpServer
 
 class HttpRedirectResolveIntegrationTest extends AbstractDependencyResolutionTest {
     @Rule SetSystemProperties systemProperties = new SetSystemProperties()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
index 728a768..3114ef9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.integtests.resolve.ivy
 
-import org.hamcrest.Matchers
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.test.fixtures.ivy.IvyFileModule
 
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.startsWith
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
@@ -33,9 +33,8 @@ class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolution
 
         buildFile << """
 repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
+    ivy { url "http://localhost:${server.port}/repo1"}
+    ivy { url "http://localhost:${server.port}/repo2"}
 }
 configurations { missing }
 dependencies {
@@ -50,23 +49,16 @@ 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")
+        expectMissingArtifact("repo1", module)
+        expectExistingArtifact("repo2", module)
 
         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.'))
+        succeeds("showMissing")
 
         when:
-        server.resetExpectations() // Missing status is cached
-        fails('showMissing')
-
+        server.resetExpectations() // Missing status in repo1 is cached
         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.'))
+        succeeds('showMissing')
     }
 
     public void "reports and recovers from broken module"() {
@@ -104,7 +96,7 @@ task showBroken << { println configurations.broken.files }
         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")
     }
@@ -191,4 +183,14 @@ task retrieve(type: Sync) {
         succeeds "retrieve"
         file('libs').assertHasDescendants('projectA-1.2.jar')
     }
+
+    def expectExistingArtifact(String repo, IvyFileModule module) {
+        server.expectGet("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/ivy-${module.getRevision()}.xml", module.ivyFile)
+        server.expectGet("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/${module.getModule()}-${module.getRevision()}.jar", module.jarFile)
+    }
+
+    def expectMissingArtifact(String repo, IvyFileModule module) {
+        server.expectGetMissing("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/ivy-${module.getRevision()}.xml")
+        server.expectHeadMissing("/${repo}/${module.getOrganisation()}/${module.getModule()}/${module.getRevision()}/${module.getModule()}-${module.getRevision()}.jar")
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
index b901fe1..9be2087 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
@@ -25,7 +25,7 @@ class IvyChangingModuleRemoteResolveIntegrationTest extends AbstractDependencyRe
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -45,12 +45,11 @@ task retrieve(type: Copy) {
 """
 
         when: "Version 1.1 is published"
-        def module = ivyRepo().module("group", "projectA", "1.1")
-        module.publish()
+        def module = ivyHttpRepo.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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         and: "We request 1.1 (changing)"
         run 'retrieve'
@@ -62,19 +61,19 @@ task retrieve(type: Copy) {
         module.artifact([name: 'other'])
         module.dependsOn("group", "projectB", "2.0")
         module.publish()
-        def moduleB = ivyRepo().module("group", "projectB", "2.0")
-        moduleB.publish();
+        def moduleB = ivyHttpRepo.module("group", "projectB", "2.0").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.expectGet('/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)
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectArtifactGet('other')
+        moduleB.expectIvyGet()
+        moduleB.expectJarGet()
 
         and: "We request 1.1 again"
         run 'retrieve'
@@ -90,7 +89,7 @@ task retrieve(type: Copy) {
         buildFile << """
 def isChanging = project.hasProperty('isChanging') ? true : false
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -106,9 +105,8 @@ task retrieve(type: Copy) {
 }
 """
         and:
-        def module = ivyRepo().module("group", "projectA", "1.1")
-        module.publish()
-        server.allowGet('/repo', ivyRepo().rootDir)
+        def module = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        module.allowAll()
 
         when: 'original retrieve'
         run 'retrieve'
@@ -117,7 +115,14 @@ task retrieve(type: Copy) {
         def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
 
         when:
+        server.resetExpectations()
         module.publishWithChangedContent()
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
 
         and:
         executer.withArguments('-PisChanging')
@@ -133,7 +138,7 @@ task retrieve(type: Copy) {
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -153,11 +158,11 @@ task retrieve(type: Copy) {
 """
 
         and:
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+        def module = ivyHttpRepo.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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         run 'retrieve'
 
@@ -171,12 +176,12 @@ task retrieve(type: Copy) {
 
         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')
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
 
         run 'retrieve'
 
@@ -192,7 +197,7 @@ task retrieve(type: Copy) {
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -215,11 +220,11 @@ task retrieve(type: Copy) {
 """
 
         when: "Version 1.1 is published"
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+        def module = ivyHttpRepo.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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         and: "We request 1.1 (changing)"
         run 'retrieve'
@@ -231,6 +236,7 @@ task retrieve(type: Copy) {
         def snapshot = jarFile.snapshot()
 
         when: "Module meta-data is changed and artifacts are modified"
+        server.resetExpectations()
         module.artifact([name: 'other'])
         module.publishWithChangedContent()
 
@@ -244,11 +250,13 @@ task retrieve(type: Copy) {
         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.expectGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+        module.expectArtifactGet('other')
 
         and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
         executer.withArguments("-PdoNotCacheChangingModules")
@@ -266,7 +274,7 @@ task retrieve(type: Copy) {
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "${ivyHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -299,11 +307,11 @@ task retrieve(type: Copy) {
 """
 
         when: "Version 1-CHANGING is published"
-        def module = ivyRepo().module("group", "projectA", "1-CHANGING").publish()
+        def module = ivyHttpRepo.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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
         and: "We request 1-CHANGING"
         run 'retrieve'
@@ -321,11 +329,13 @@ task retrieve(type: Copy) {
         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.expectGet('/repo/group/projectA/1-CHANGING/other-1-CHANGING.jar', module.moduleDir.file('other-1-CHANGING.jar'))
+        module.expectIvyHead()
+        module.expectIvySha1Get()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1Get()
+        module.expectJarGet()
+        module.expectArtifactGet('other')
 
         and: "We request 1-CHANGING again"
         executer.withArguments()
@@ -343,7 +353,7 @@ task retrieve(type: Copy) {
         given:
         buildFile << """
             repositories {
-                ivy { url "http://localhost:${server.port}/repo" }
+                ivy { url "${ivyHttpRepo.uri}" }
             }
 
             configurations { compile }
@@ -363,28 +373,19 @@ task retrieve(type: Copy) {
         """
 
         and:
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+        def module = ivyHttpRepo.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)
+        module.expectIvyGet()
+        module.expectJarGet()
 
+        and:
         run 'retrieve'
 
         then:
@@ -392,27 +393,28 @@ task retrieve(type: Copy) {
         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.expectHead(ivyPath, module.ivyFile, originalIvyLastMod, originalIvyContentLength)
-        server.expectHead(jarPath, module.jarFile, originalJarLastMod, originalJarContentLength)
+        module.expectIvyHead()
+        module.expectJarHead()
 
+        and:
         run 'retrieve'
 
         then:
         downloadedJar.assertHasNotChangedSince(snapshot)
 
         when:
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
         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)
+        module.expectIvyHead()
+        module.expectIvySha1GetMissing()
+        module.expectIvyGet()
+        module.expectJarHead()
+        module.expectJarSha1GetMissing()
+        module.expectJarGet()
 
         run 'retrieve'
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
index 7f63c13..92f6fcd 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
@@ -15,20 +15,18 @@
  */
 package org.gradle.integtests.resolve.ivy
 
-import org.gradle.integtests.fixtures.IvyModule
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class IvyDynamicRevisionRemoteResolveIntegrationTest 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}"
+        url "${ivyHttpRepo.uri}"
     }
 }
 
@@ -50,13 +48,17 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        ivyHttpRepo.module("group", "projectA", "2.0").publish()
+        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").publish()
 
         and: "Server handles requests"
-        serveUpDynamicRevision(projectA1)
-        serveUpDynamicRevision(projectB1)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA1.expectIvyGet()
+        projectA1.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB1.expectIvyGet()
+        projectB1.expectJarGet()
 
         and:
         run 'retrieve'
@@ -67,13 +69,17 @@ task retrieve(type: Sync) {
         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()
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
 
         and: "Server handles requests"
         server.resetExpectations()
-        serveUpDynamicRevision(projectA2)
-        serveUpDynamicRevision(projectB2)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA2.expectIvyGet()
+        projectA2.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB2.expectIvyGet()
+        projectB2.expectJarGet()
 
         and:
         run 'retrieve'
@@ -84,16 +90,14 @@ task retrieve(type: Sync) {
         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}"
+      url "${ivyHttpRepo.uri}"
   }
 }
 
@@ -110,19 +114,19 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def projectA11 = ivyHttpRepo.module("group", "projectA", "1.1").withNoMetaData().publish()
+        def projectA12 = ivyHttpRepo.module("group", "projectA", "1.2").withNoMetaData().publish()
+        ivyHttpRepo.module("group", "projectA", "2.0").withNoMetaData().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")
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGetMissing()
+        projectA11.expectIvyGetMissing()
 
-        // 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)
+        // TODO - Should not list twice
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA12.expectJarHead()
+        projectA12.expectJarGet()
 
         and:
         run 'retrieve'
@@ -133,13 +137,12 @@ task retrieve(type: Sync) {
 
     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"
+        url "${ivyHttpRepo.uri}"
     }
 }
 
@@ -148,17 +151,11 @@ configurations {
     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'
@@ -171,36 +168,105 @@ task retrieveMilestone(type: Sync) {
 """
 
         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()
+        ivyHttpRepo.module("group", "projectA", "1.0").withStatus('release').publish()
+        ivyHttpRepo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        ivyHttpRepo.module('group', 'projectA', '1.2').withStatus('integration').publish()
+        def release = ivyHttpRepo.module("group", "projectA", "2.0").withStatus('release').publish()
+        def milestone = ivyHttpRepo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
+        def integration = ivyHttpRepo.module('group', 'projectA', '2.2').withStatus('integration').publish()
 
         and: "Server handles requests"
-        server.allowGet('/repo', repo.rootDir)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        integration.expectIvyGet()
+        milestone.expectIvyGet()
+        release.expectIvyGet()
+        release.expectJarGet()
 
         and:
-        run 'retrieve'
+        run 'retrieveRelease'
 
         then:
         file('release').assertHasDescendants('projectA-2.0.jar')
+
+        when:
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        integration.expectIvyHead()
+        milestone.expectIvyHead()
+        milestone.expectJarGet()
+
+        and:
+        run 'retrieveMilestone'
+
+        then:
         file('milestone').assertHasDescendants('projectA-2.1.jar')
     }
 
+    def "can use latest version from different remote repositories"() {
+        server.start()
+        def repo1 = ivyHttpRepo("ivy1")
+        def repo2 = ivyHttpRepo("ivy2")
+
+        given:
+        buildFile << """
+    repositories {
+        ivy {
+            url "${repo1.uri}"
+        }
+        ivy {
+            url "${repo2.uri}"
+        }
+    }
+
+    configurations {
+        milestone
+    }
+
+    dependencies {
+        milestone group: "group", name: "projectA", version: "latest.milestone"
+    }
+
+    task retrieveMilestone(type: Sync) {
+        from configurations.milestone
+        into 'milestone'
+    }
+    """
+
+        when: "Versions are published"
+        def version11 = repo1.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        def version12 = repo2.module('group', 'projectA', '1.2').withStatus('integration').publish()
+
+        and: "Server handles requests"
+        repo1.expectDirectoryListGet("group", "projectA")
+        version11.expectIvyGet()
+        version11.expectJarGet()
+        repo2.expectDirectoryListGet("group", "projectA")
+        version12.expectIvyGet()
+        // TODO - shouldn't need this
+        repo2.expectDirectoryListGet("group", "projectA")
+        // TODO - shouldn't need this
+        version12.expectJarGet()
+
+        and:
+        run 'retrieveMilestone'
+
+        then:
+        file('milestone').assertHasDescendants('projectA-1.1.jar')
+    }
+
     def "checks new repositories before returning any cached value"() {
         server.start()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
 
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
+    ivy { url "${repo1.uri}" }
 }
 
 if (project.hasProperty('addRepo2')) {
     repositories {
-        ivy { url "http://localhost:${server.port}/repo2" }
+        ivy { url "${repo2.uri}" }
     }
 }
 
@@ -217,13 +283,13 @@ task retrieve(type: Sync) {
 """
 
         when:
-        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
-        projectA11.publish()
-        def projectA12 = ivyRepo('repo2').module("group", "projectA", "1.2")
-        projectA12.publish()
+        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo2.module("group", "projectA", "1.2").publish()
 
         and: "Server handles requests"
-        serveUpDynamicRevision(projectA11, "/repo1")
+        repo1.expectDirectoryListGet("group", "projectA")
+        projectA11.expectIvyGet()
+        projectA11.expectJarGet()
 
         and: "Retrieve with only repo1"
         run 'retrieve'
@@ -233,7 +299,9 @@ task retrieve(type: Sync) {
 
         when: "Server handles requests"
         server.resetExpectations()
-        serveUpDynamicRevision(projectA12, "/repo2")
+        repo2.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
 
         and: "Retrieve with both repos"
         executer.withArguments("-PaddRepo2")
@@ -243,15 +311,71 @@ task retrieve(type: Sync) {
         file('libs').assertHasDescendants('projectA-1.2.jar')
     }
 
+    def "does not cache information about broken modules"() {
+        server.start()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+
+        given:
+        buildFile << """
+    repositories {
+        ivy { url "${repo1.uri}" }
+        ivy { url "${repo2.uri}" }
+    }
+
+    configurations { compile }
+
+    dependencies {
+        compile group: "group", name: "projectA", version: "1.+"
+    }
+
+    task retrieve(type: Sync) {
+        from configurations.compile
+        into 'libs'
+    }
+    """
+
+        when:
+        def projectA12 = repo1.module("group", "projectA", "1.2").publish()
+        def projectA11 = repo2.module("group", "projectA", "1.1").publish()
+
+        and: "projectA is broken in repo1"
+        server.addBroken("/repo1/group/projectA/")
+        repo2.expectDirectoryListGet("group", "projectA")
+        projectA11.expectIvyGet()
+        projectA11.expectJarGet()
+
+        and: "Retrieve with only repo2"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        repo1.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and: "Retrieve with both repos"
+        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()
+        def repo1 = ivyHttpRepo("repo1")
+        def repo2 = ivyHttpRepo("repo2")
+        def repo3 = ivyHttpRepo("repo3")
 
         given:
         buildFile << """
 repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
-    ivy { url "http://localhost:${server.port}/repo2" }
-    ivy { url "http://localhost:${server.port}/repo3" }
+    ivy { url "${repo1.uri}" }
+    ivy { url "${repo2.uri}" }
+    ivy { url "${repo3.uri}" }
 }
 
 configurations { compile }
@@ -267,19 +391,19 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def projectA11 = repo1.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo3.module("group", "projectA", "1.2").publish()
 
         and: "Server handles requests"
-        server.expectGetDirectoryListing("/repo1/group/projectA/", projectA11.moduleDir.parentFile)
+        repo1.expectDirectoryListGet("group", "projectA")
         // TODO Should not need to get this
-        server.expectGet("/repo1/group/projectA/1.1/ivy-1.1.xml", projectA11.ivyFile)
+        projectA11.expectIvyGet()
         // TODO Should only list missing directory once
-        server.expectGetMissing("/repo2/group/projectA/")
-        server.expectGetMissing("/repo2/group/projectA/")
-        serveUpDynamicRevision(projectA12, "/repo3")
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo2.expectDirectoryListGet("group", "projectA")
+        repo3.expectDirectoryListGet("group", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
 
         and:
         run 'retrieve'
@@ -295,15 +419,67 @@ task retrieve(type: Sync) {
         result.assertTaskSkipped(':retrieve')
     }
 
+    def "reuses cached artifacts that match multiple dynamic versions"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "${ivyHttpRepo.uri}" }
+}
+
+configurations { deps1; deps2 }
+
+dependencies {
+    deps1 group: "org.test", name: "projectA", version: "1.+"
+    deps2 group: "org.test", name: "projectA", version: "[1.0,2.0)"
+}
+
+task retrieve1(type: Sync) {
+    from configurations.deps1
+    into 'libs1'
+}
+task retrieve2(type: Sync) {
+    from configurations.deps2
+    into 'libs2'
+}
+"""
+
+        when:
+        ivyHttpRepo.module("org.test", "projectA", "1.1").publish()
+        def projectA12 = ivyHttpRepo.module("org.test", "projectA", "1.2").publish()
+
+        and:
+        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA12.expectIvyGet()
+        projectA12.expectJarGet()
+
+        and:
+        run 'retrieve1'
+
+        then:
+        file('libs1').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA12.expectIvyHead()
+
+        and:
+        run 'retrieve2'
+
+        then:
+        file('libs1').assertHasDescendants('projectA-1.2.jar')
+    }
+
     def "caches resolved revisions until cache expiry"() {
         server.start()
-        def repo = ivyRepo()
 
         given:
         buildFile << """
 repositories {
     ivy {
-        url "http://localhost:${server.port}"
+        url "${ivyHttpRepo.uri}"
     }
 }
 
@@ -326,11 +502,12 @@ task retrieve(type: Sync) {
 """
 
         when: "Version 1.1 is published"
-        def version1 = repo.module("group", "projectA", "1.1")
-        version1.publish()
+        def version1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
 
         and: "Server handles requests"
-        serveUpDynamicRevision(version1)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        version1.expectIvyGet()
+        version1.expectJarGet()
 
         and: "We request 1.+"
         run 'retrieve'
@@ -340,8 +517,7 @@ task retrieve(type: Sync) {
         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()
+        def version2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
 
         and: "We request 1.+, with dynamic mappings cached. No server requests."
         run 'retrieve'
@@ -351,10 +527,12 @@ task retrieve(type: Sync) {
         file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
 
         when: "Server handles requests"
-        serveUpDynamicRevision(version2)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        version2.expectIvyGet()
+        version2.expectJarGet()
 
         and: "We request 1.+, with zero expiry for dynamic revision cache"
-        executer.withArguments("-d", "-PnoDynamicRevisionCache").withTasks('retrieve').run()
+        executer.withArguments("-PnoDynamicRevisionCache").withTasks('retrieve').run()
 
         then: "Version 1.2 is used"
         file('libs').assertHasDescendants('projectA-1.2.jar')
@@ -363,13 +541,12 @@ task retrieve(type: Sync) {
 
     def "uses and caches dynamic revisions for transitive dependencies"() {
         server.start()
-        def repo = ivyRepo()
 
         given:
         buildFile << """
 repositories {
     ivy {
-        url "http://localhost:${server.port}"
+        url "${ivyHttpRepo.uri}"
     }
 }
 
@@ -392,22 +569,24 @@ task retrieve(type: Sync) {
 """
 
         when: "Version is published"
-        def mainProject = repo.module("group", "main", "1.0")
+        def mainProject = ivyHttpRepo.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()
+        def projectA1 = ivyHttpRepo.module("group", "projectA", "1.1").publish()
+        def projectB1 = ivyHttpRepo.module("group", "projectB", "1.1").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)
+        mainProject.expectIvyGet()
+        mainProject.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA1.expectIvyGet()
+        projectA1.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB1.expectIvyGet()
+        projectB1.expectJarGet()
 
         and:
         run 'retrieve'
@@ -418,10 +597,8 @@ task retrieve(type: Sync) {
         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()
+        def projectA2 = ivyHttpRepo.module("group", "projectA", "1.2").publish()
+        def projectB2 = ivyHttpRepo.module("group", "projectB", "2.2").publish()
 
         and: "No server requests"
         server.resetExpectations()
@@ -436,8 +613,12 @@ task retrieve(type: Sync) {
 
         when: "Server handles requests"
         server.resetExpectations()
-        serveUpDynamicRevision(projectA2)
-        serveUpDynamicRevision(projectB2)
+        ivyHttpRepo.expectDirectoryListGet("group", "projectA")
+        projectA2.expectIvyGet()
+        projectA2.expectJarGet()
+        ivyHttpRepo.expectDirectoryListGet("group", "projectB")
+        projectB2.expectIvyGet()
+        projectB2.expectJarGet()
 
         and: "DynamicRevisionCache is bypassed"
         executer.withArguments("-PnoDynamicRevisionCache")
@@ -449,9 +630,110 @@ task retrieve(type: Sync) {
         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)
+    public void "resolves dynamic version with 2 repositories where first repo results in 404 for directory listing"() {
+        server.start()
+        given:
+        def repo = ivyHttpRepo("repo2")
+        def moduleA = repo.module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                ivy { url "http://localhost:${server.port}/repo1" }
+                ivy { url "${repo.uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile 'group:projectA:1.+'
+            }
+            task listJars << {
+                assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+            }
+            """
+
+        when:
+        server.expectGetMissing('/repo1/group/projectA/')
+        // TODO - should only list versions once
+        server.expectGetMissing('/repo1/group/projectA/')
+        repo.expectDirectoryListGet("group", "projectA")
+        moduleA.expectIvyGet()
+        moduleA.expectJarGet()
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+        then:
+        succeeds('listJars')
+    }
+
+    def "reuses cached artifacts across repository types"() {
+        server.start()
+        def ivyRepo = ivyHttpRepo('repo1')
+        def mavenRepo = mavenHttpRepo('repo2')
+        def ivyModule = ivyRepo.module("org.test", "a", "1.1").publish()
+        def mavenModule = mavenRepo.module("org.test", "a", "1.1").publish()
+        assert ivyModule.jarFile.bytes == mavenModule.artifactFile.bytes
+
+        given:
+        buildFile.text = """
+repositories {
+    ivy { url '${ivyRepo.uri}' }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:a:1+'
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when:
+        ivyRepo.expectDirectoryListGet("org.test", "a")
+        ivyModule.expectIvyGet()
+        ivyModule.expectJarGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('a-1.1.jar')
+
+        when:
+        buildFile.text = """
+repositories {
+    maven { url '${mavenRepo.uri}' }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org.test:a:[1.0,2.0)'
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        and:
+        mavenRepo.expectMetaDataGet("org.test", "a")
+        mavenModule.expectPomGet()
+        mavenModule.expectArtifactHead()
+        mavenModule.expectArtifactSha1Get()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('a-1.1.jar')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
new file mode 100644
index 0000000..ab7c953
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionResolveIntegrationTest.groovy
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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
+import spock.lang.Ignore
+import spock.lang.Issue
+
+class IvyDynamicRevisionResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Ignore
+    @Issue("GRADLE-2502")
+    public void "latest.integration selects highest version regardless of status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.integration'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:group, module:projectA, version:latest.integration.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('integration').publish()
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('integration').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.4').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.4.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    public void "latest.milestone selects highest version with milestone or release status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.milestone'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.milestone.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('milestone').publish()
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('milestone').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    public void "latest.release selects highest version with release status"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.release'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '2.0').withNoMetaData().publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').withStatus('integration').publish()
+        ivyRepo.module('org.test', 'projectA', '1.2').withStatus('milestone').publish()
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:latest.release.'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.0').withStatus('release').publish()
+        ivyRepo.module('org.test', 'projectA', '1.1').withStatus('release').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+    }
+
+    @Ignore
+    @Issue("GRADLE-2502")
+    public void "version selector ending in + selects highest matching version"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:1.2+'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+        and:
+        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
+        ivyRepo.module('org.test', 'projectA', '2.0').publish()
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:1.2+'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.9').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.9.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.12').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.12.jar')
+    }
+
+    @Ignore
+    @Issue("GRADLE-2502")
+    public void "version range selects highest matching version"() {
+        given:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${ivyRepo.uri}"
+      }
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:[1.2,2.0]'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+        and:
+        ivyRepo.module('org.test', 'projectA', '1.1.2').publish()
+        ivyRepo.module('org.test', 'projectA', '2.0').publish()
+
+        when:
+        runAndFail 'retrieve'
+
+        then:
+        failureHasCause 'Could not find any version that matches group:org.test, module:projectA, version:[1.2,2.0]'
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.2.1').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.1.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3').publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.jar')
+
+        when:
+        ivyRepo.module('org.test', 'projectA', '1.3.12').withNoMetaData().publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.3.12.jar')
+    }
+
+    @Issue("GRADLE-2502")
+    public void "can resolve dynamic version from different repositories"() {
+        given:
+        def repo1 = ivyRepo("ivyRepo1")
+        def repo2 = ivyRepo("ivyRepo2")
+        repo1.module('org.test', 'projectA', '1.1').withStatus("milestone").publish()
+        repo2.module('org.test', 'projectA', '1.2').withStatus("integration").publish()
+
+        and:
+        buildFile << """
+  repositories {
+      ivy {
+          url "${repo1.uri}"
+      }
+      ivy {
+          url "${repo2.uri}"
+      }
+
+  }
+  configurations { compile }
+  dependencies {
+      compile 'org.test:projectA:latest.milestone'
+  }
+  task retrieve(type: Sync) {
+      from configurations.compile
+      into 'libs'
+  }
+  """
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
index 5b50f67..0c1475a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
@@ -15,10 +15,14 @@
  */
 package org.gradle.integtests.resolve.ivy
 
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.junit.Rule
 
 class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
+    @Rule ProgressLoggingFixture progressLogger
+
     public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
         server.start()
         given:
@@ -38,14 +42,12 @@ task listJars << {
         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'
-
+        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/ivy-1.2.xml")
+        progressLogger.downloadProgressLogged("http://localhost:${server.port}/repo/group/projectA/1.2/projectA-1.2.jar")
         when:
         server.resetExpectations()
-        // No extra calls for cached dependencies
-
         then:
         succeeds 'listJars'
     }
@@ -74,6 +76,7 @@ task listJars << {
         server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
 
         then:
+        executer.withArgument("-i")
         succeeds('listJars')
 
         when:
@@ -81,6 +84,7 @@ task listJars << {
         // No extra calls for cached dependencies
 
         then:
+        executer.withArgument("-i")
         succeeds('listJars')
     }
 
@@ -122,10 +126,8 @@ task listJars << {
         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
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..ab1bc8a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpsRepoResolveIntegrationTest.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.resolve.ivy
+
+import org.gradle.integtests.resolve.http.AbstractHttpsRepoResolveIntegrationTest
+
+class IvyHttpsRepoResolveIntegrationTest extends AbstractHttpsRepoResolveIntegrationTest {
+    protected String setupRepo() {
+        def module = ivyRepo().module('my-group', 'my-module').publish()
+        server.allowGetOrHead('/repo1/my-group/my-module/1.0/ivy-1.0.xml', module.ivyFile)
+        server.allowGetOrHead('/repo1/my-group/my-module/1.0/my-module-1.0.jar', module.jarFile)
+        "ivy"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
index e5b1d7a..ea90001 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
@@ -19,23 +19,24 @@ package org.gradle.integtests.resolve.maven
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
     def "can resolve dynamic version declared in pom as transitive dependency from HTTP Maven repository"() {
         given:
         server.start()
 
-        mavenRepo().module('group', 'projectC', '1.1').publish()
-        def projectC = mavenRepo().module('group', 'projectC', '1.5').publish()
-        mavenRepo().module('group', 'projectC', '2.0').publish()
-        def projectB = mavenRepo().module('group', 'projectB').dependsOn("group",'projectC', '[1.0, 2.0)').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+        mavenHttpRepo.module('org.test', 'projectC', '1.1').publish()
+        def projectC = mavenHttpRepo.module('org.test', 'projectC', '1.5').publish()
+        mavenHttpRepo.module('org.test', 'projectC', '2.0').publish()
+        def projectB = mavenHttpRepo.module('org.test', 'projectB', '1.0').dependsOn("org.test", 'projectC', '[1.0, 2.0)').publish()
+        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.0').dependsOn('org.test', 'projectB', '1.0').publish()
 
         buildFile << """
     repositories {
-        maven { url 'http://localhost:${server.port}/repo1' }
+        maven { url '${mavenHttpRepo.uri}' }
     }
     configurations { compile }
     dependencies {
-        compile 'group:projectA:1.0'
+        compile 'org.test:projectA:1.0'
     }
 
     task retrieve(type: Sync) {
@@ -45,13 +46,13 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
     """
 
         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)
-        server.expectGet('/repo1/group/projectC/maven-metadata.xml', projectC.rootMetaDataFile)
-        server.expectGet('/repo1/group/projectC/1.5/projectC-1.5.pom', projectC.pomFile)
-        server.expectGet('/repo1/group/projectC/1.5/projectC-1.5.jar', projectC.artifactFile)
+        projectA.expectPomGet()
+        projectA.expectArtifactGet()
+        projectB.expectPomGet()
+        projectB.expectArtifactGet()
+        mavenHttpRepo.expectMetaDataGet("org.test", "projectC")
+        projectC.expectPomGet()
+        projectC.expectArtifactGet()
 
         and:
         run 'retrieve'
@@ -68,4 +69,98 @@ class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTes
         then:
         file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
     }
+
+    def "falls back to directory listing when maven-metadata.xml is missing"() {
+        given:
+        server.start()
+        mavenHttpRepo.module('org.test', 'projectA', '1.0').publish()
+        def projectA = mavenHttpRepo.module('org.test', 'projectA', '1.5').publish()
+
+        buildFile << """
+    repositories {
+        maven { url '${mavenHttpRepo.uri}' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'org.test:projectA:1.+'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        mavenHttpRepo.expectMetaDataGetMissing("org.test", "projectA")
+        mavenHttpRepo.expectDirectoryListGet("org.test", "projectA")
+        projectA.expectPomGet()
+        projectA.expectArtifactGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.5.jar')
+        def snapshot = file('libs/projectA-1.5.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.5.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "does not cache broken module information"() {
+        given:
+        server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
+        def projectA1 = repo1.module('group', 'projectA', '1.1').publish()
+        def projectA2 = repo2.module('group', 'projectA', '1.5').publish()
+
+        buildFile << """
+        repositories {
+            maven { url '${repo1.uri}' }
+            maven { url '${repo2.uri}' }
+        }
+        configurations { compile }
+        dependencies {
+            compile 'group:projectA:1.+'
+        }
+
+        task retrieve(type: Sync) {
+            into 'libs'
+            from configurations.compile
+        }
+        """
+
+        when:
+        repo1.expectMetaDataGet("group", "projectA")
+        projectA1.expectPomGet()
+        projectA1.expectArtifactGet()
+
+        repo2.expectMetaDataGet("group", "projectA")
+        server.addBroken('/repo2/group/projectA/1.5/projectA-1.5.pom')
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when:
+        server.resetExpectations()
+        repo2.expectMetaDataGet("group", "projectA")
+        projectA2.expectPomGet()
+        projectA2.expectArtifactGet()
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.5.jar')
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
index 7ef9f59..8ccd851 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests.resolve.maven
 
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class MavenFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
@@ -100,7 +99,7 @@ task retrieve(type: Sync) {
         moduleA.publish()
         moduleB.publish()
 
-        def artifactsRepo = new MavenRepository(distribution.testFile('artifactsRepo'));
+        def artifactsRepo = mavenRepo('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)
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
index af069ee..2eb0590 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
@@ -15,25 +15,13 @@
  */
 package org.gradle.integtests.resolve.maven
 
-import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.util.TestFile
 import org.junit.Rule
 
 class MavenHttpRepoResolveIntegrationTest 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() }
-    }
+    @Rule ProgressLoggingFixture progressLogging
 
     def "can resolve and cache dependencies from HTTP Maven repository"() {
         given:
@@ -65,16 +53,22 @@ task retrieve(type: Sync) {
 
         and:
         run 'retrieve'
-        
+
         then:
         file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
         def snapshot = file('libs/projectA-1.0.jar').snapshot()
-        
+
+        and:
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.pom")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectA/1.0/projectA-1.0.jar")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.pom")
+        progressLogging.downloadProgressLogged("http://localhost:${server.port}/repo1/group/projectB/1.0/projectB-1.0.jar")
+
         when:
         server.resetExpectations()
         and:
         run 'retrieve'
-        
+
         then:
         file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
     }
@@ -279,13 +273,13 @@ task listJars << {
     }
 
     def "can resolve and cache dependencies from HTTP Maven repository with invalid settings.xml"() {
-            given:
-            server.start()
+        given:
+        server.start()
 
-            def projectB = mavenRepo().module('group', 'projectB').publish()
-            def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
 
-            buildFile << """
+        buildFile << """
     repositories {
         maven { url 'http://localhost:${server.port}/repo1' }
     }
@@ -300,31 +294,31 @@ task listJars << {
     }
     """
 
-            def m2Home = file("M2_REPO")
-            def settingsFile = m2Home.file("conf/settings.xml")
-            settingsFile << "invalid content... blabla"
+        def m2Home = file("M2_REPO")
+        def settingsFile = m2Home.file("conf/settings.xml")
+        settingsFile << "invalid content... blabla"
 
-            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)
+        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:
+        and:
 
-            executer.withEnvironmentVars(M2_HOME:m2Home.absolutePath)
-            run 'retrieve'
+        executer.withEnvironmentVars(M2_HOME: m2Home.absolutePath)
+        run 'retrieve'
 
-            then:
-            file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
-            def snapshot = file('libs/projectA-1.0.jar').snapshot()
+        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'
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
 
-            then:
-            file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-        }
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..1435787
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpsRepoResolveIntegrationTest.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.resolve.maven
+
+import org.gradle.integtests.resolve.http.AbstractHttpsRepoResolveIntegrationTest
+
+class MavenHttpsRepoResolveIntegrationTest extends AbstractHttpsRepoResolveIntegrationTest {
+    protected String setupRepo() {
+        def module = mavenRepo().module('my-group', 'my-module').publish()
+        server.allowGetOrHead('/repo1/my-group/my-module/1.0/my-module-1.0.pom', module.pomFile)
+        server.allowGetOrHead('/repo1/my-group/my-module/1.0/my-module-1.0.jar', module.artifactFile)
+        "maven"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
index 344bfab..e59b1a7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
@@ -18,13 +18,10 @@ package org.gradle.integtests.resolve.maven
 import org.gradle.integtests.fixture.M2Installation
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.MavenModule
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.util.SetSystemProperties
-import org.gradle.util.TestFile
 import org.junit.Rule
 
 import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.not
 
 class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
@@ -47,11 +44,11 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
                 }"""
     }
 
-    public void "can resolve artifacts from local m2 with not existing user settings.xml"() {
+    public void "can resolve artifacts from local m2 when user settings.xml does not exist"() {
         given:
         def m2 = localM2()
-        def moduleA = m2.mavenRepo().module('group', 'projectA', '1.2')
-        moduleA.publish()
+        def moduleA = m2.mavenRepo().module('group', 'projectA', '1.2').publish()
+
         and:
         withM2(m2)
 
@@ -63,68 +60,50 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
     }
 
-    public void "can resolve artifacts from local m2 with custom localRepository defined in user settings.xml"() {
+    public void "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
         given:
-        def artifactRepo = new MavenRepository(file("artifactrepo"))
-        def m2 = localM2() {
-            userSettingsFile << """<settings>
-                        <localRepository>${artifactRepo.rootDir.absolutePath}</localRepository>
-                    </settings>"""
-        }
-        def moduleA = artifactRepo.module('group', 'projectA', '1.2')
-        moduleA.publish()
+        def artifactRepo = maven("artifactrepo")
+        def m2 = localM2().generateUserSettingsFile(artifactRepo)
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
 
-        when:
+        and:
         withM2(m2)
+
+        when:
         run 'retrieve'
 
         then:
         hasArtifact(moduleA)
     }
 
-    public void "can resolve artifacts from local m2 with custom localRepository defined in global settings.xml"() {
+    public void "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
         given:
-        def artifactRepo = new MavenRepository(file("artifactrepo"))
-        def m2 = localM2() {
-            createGlobalSettingsFile(file("global_M2")) << """<settings>
-                        <localRepository>${artifactRepo.rootDir.absolutePath}</localRepository>
-                    </settings>"""
-        }
+        def artifactRepo = maven("artifactrepo")
+        def m2 = localM2().generateGlobalSettingsFile(artifactRepo)
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2').publish()
 
-        def moduleA = artifactRepo.module('group', 'projectA', '1.2')
-        moduleA.publish()
+        and:
+        withM2(m2)
 
         when:
-        withM2(m2)
         run 'retrieve'
 
         then:
         hasArtifact(moduleA)
     }
 
-    public void "localRepository in user settings take precedence over the localRepository global settings"() {
+    public void "local repository in user settings take precedence over the local repository global settings"() {
         given:
-        def globalRepo = new MavenRepository(file("globalArtifactRepo"))
-        def userRepo = new MavenRepository(file("userArtifactRepo"))
-        def m2 = localM2() {
-            createGlobalSettingsFile(file("global_M2")) << """<settings>
-                            <localRepository>${globalRepo.rootDir.absolutePath}</localRepository>
-                        </settings>"""
-            userSettingsFile << """<settings>
-                                    <localRepository>${userRepo.rootDir.absolutePath}</localRepository>
-                                </settings>"""
-
-        }
+        def globalRepo = maven("globalArtifactRepo")
+        def userRepo = maven("userArtifactRepo")
+        def m2 = localM2().generateGlobalSettingsFile(globalRepo).generateUserSettingsFile(userRepo)
+        def moduleA = userRepo.module('group', 'projectA', '1.2').publish()
+        globalRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
 
-        def moduleA = userRepo.module('group', 'projectA', '1.2')
-        moduleA.publish()
-
-        def globalModuleA = globalRepo.module('group', 'projectA', '1.2')
-        globalModuleA.publishWithChangedContent() // to ensure that resulting artifact
-                                                  // has different hash than userModuleA.artifactFile
+        and:
+        withM2(m2)
 
         when:
-        withM2(m2)
         run 'retrieve'
 
         then:
@@ -133,12 +112,13 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
     public void "fail with meaningful error message if settings.xml is invalid"() {
         given:
-        def m2 = localM2() {
-            userSettingsFile << "invalid content"
-        }
+        def m2 = localM2()
+        m2.userSettingsFile << "invalid content"
 
-        when:
+        and:
         withM2(m2)
+
+        when:
         def failure = runAndFail('retrieve')
 
         then:
@@ -147,17 +127,21 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
     public void "mavenLocal is ignored if no local maven repository exists"() {
         given:
-        def anotherRepo = new MavenRepository(file("another-local-repo"))
-        def moduleA = anotherRepo.module('group', 'projectA', '1.2')
-        moduleA.publishWithChangedContent();
+        def m2 = localM2()
+        def anotherRepo = maven("another-local-repo")
+        def moduleA = anotherRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
 
-        when:
+        and:
         buildFile << """
         repositories{
             maven { url "${anotherRepo.uri}" }
         }
         """
 
+        and:
+        withM2(m2)
+
+        when:
         run 'retrieve'
 
         then:
@@ -166,31 +150,19 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
     def hasArtifact(MavenModule module) {
         def buildDir = file('build')
-        def artifactName = module.artifactFile.getName()
+        def artifactName = module.artifactFile.name
         buildDir.assertHasDescendants(artifactName)
         buildDir.file(artifactName).assertIsCopyOf(module.artifactFile)
     }
 
-
     def withM2(M2Installation m2) {
-        def args = ["-Duser.home=${m2.userM2Directory.parentFile.absolutePath}".toString()]
+        executer.withUserHomeDir(m2.userHomeDir)
         if (m2.globalMavenDirectory?.exists()) {
             executer.withEnvironmentVars(M2_HOME:m2.globalMavenDirectory.absolutePath)
         }
-        executer.withArguments(args)
     }
 
     M2Installation localM2() {
-        TestFile testUserHomeDir = distribution.getUserHomeDir()
-        TestFile userM2Dir = testUserHomeDir.file(".m2")
-        new M2Installation(userM2Dir)
-    }
-
-    M2Installation localM2(Closure configClosure) {
-        M2Installation m2 = localM2()
-        configClosure.setDelegate(m2)
-        configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
-        configClosure.call()
-        m2
+        new M2Installation(testDir)
     }
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
index af15b46..39d24e3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
@@ -15,13 +15,13 @@
  */
 package org.gradle.integtests.resolve.maven
 
+import org.gradle.integtests.fixtures.MavenFileModule
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Issue
-import org.gradle.integtests.fixtures.MavenModule
 import spock.lang.FailsWith
+import spock.lang.Issue
 
 class MavenPomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    MavenModule projectA
+    MavenFileModule projectA
 
     def setup() {
         server.start()
@@ -138,7 +138,7 @@ task retrieve(type: Sync) {
         file('libs/projectA-1.0.custom').assertIsCopyOf(projectA.artifactFile)
 
         and:
-        result.output.contains("Deprecated: relying on packaging to define the extension of the main artifact is deprecated")
+        result.output.contains("Relying on packaging to define the extension of the main artifact has been deprecated")
     }
 
     def "fails and reports type-based location if neither packaging-based or type-based artifact can be located"() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
index 79ed593..03b6805 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
@@ -15,20 +15,21 @@
  */
 package org.gradle.integtests.resolve.maven
 
-import org.gradle.integtests.fixtures.MavenModule
-import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.MavenHttpModule
 import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
 
 class MavenSnapshotResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
     def "can find and cache snapshots in multiple Maven HTTP repositories"() {
         server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
 
         given:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo1" }
-    maven { url "http://localhost:${server.port}/repo2" }
+    maven { url "${repo1.uri}" }
+    maven { url "${repo2.uri}" }
 }
 
 configurations { compile }
@@ -46,20 +47,22 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def repo1ProjectA = repo1.module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def repo1ProjectB = repo1.module("org.gradle", "projectB", "1.0-SNAPSHOT")
+        def repo2ProjectB = repo2.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+        def repo1NonUnique = repo1.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
+        def repo2NonUnique = repo2.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
 
         when: "Server provides projectA from repo1"
-        expectModuleServed(projectA, '/repo1')
+        expectModuleServed(repo1ProjectA)
 
         and: "Server provides projectB from repo2"
-        expectModuleMissing(projectB, '/repo1')
-        expectModuleServed(projectB, '/repo2')
+        expectModuleMissing(repo1ProjectB)
+        expectModuleServed(repo2ProjectB)
 
         and: "Server provides nonunique snapshot from repo2"
-        expectModuleMissing(nonUnique, '/repo1')
-        expectModuleServed(nonUnique, '/repo2')
+        expectModuleMissing(repo1NonUnique)
+        expectModuleServed(repo2NonUnique)
 
         and: "We resolve dependencies"
         run 'retrieve'
@@ -81,13 +84,15 @@ task retrieve(type: Sync) {
 
     def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
         server.start()
+        def repo1 = mavenHttpRepo("repo1")
+        def repo2 = mavenHttpRepo("repo2")
 
         given:
         buildFile << """
 repositories {
     maven {
-        url "http://localhost:${server.port}/repo1"
-        artifactUrls "http://localhost:${server.port}/repo2"
+        url "${repo1.uri}"
+        artifactUrls "${repo2.uri}"
     }
 }
 
@@ -105,22 +110,25 @@ task retrieve(type: Sync) {
 """
 
         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 projectA = repo1.module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def repo1ProjectB = repo1.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+        def repo2ProjectB = repo2.module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
 
         when: "Server provides projectA from repo1"
-        expectModuleServed(projectA, '/repo1')
+        expectModuleServed(projectA)
 
         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}")
+        repo1ProjectB.expectMetaDataGet()
+        repo1ProjectB.expectPomGet()
+        // TODO - should fetch this once
+        repo1ProjectB.expectMetaDataGet()
+
+        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${repo1ProjectB.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)
+        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar", repo2ProjectB.artifactFile)
+//        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/${repo2ProjectB.artifactFile.name}", repo2ProjectB.artifactFile)
 
         and: "We resolve dependencies"
         run 'retrieve'
@@ -145,7 +153,7 @@ task retrieve(type: Sync) {
 
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -163,12 +171,12 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def uniqueVersionModule = mavenHttpRepo.module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
 
         and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo', false, false)
-        expectModuleServed(nonUniqueVersionModule, '/repo', false, false)
+        expectModuleServed(uniqueVersionModule)
+        expectModuleServed(nonUniqueVersionModule)
 
         and: "We resolve dependencies"
         run 'retrieve'
@@ -184,8 +192,8 @@ task retrieve(type: Sync) {
         nonUniqueVersionModule.artifactFile << 'more content'
 
         and: "No server requests"
-        expectChangedArtifactServed(uniqueVersionModule, '/repo')
-        expectChangedArtifactServed(nonUniqueVersionModule, '/repo')
+        expectChangedArtifactServed(uniqueVersionModule)
+        expectChangedArtifactServed(nonUniqueVersionModule)
 
         and: "Resolve dependencies again"
         run 'retrieve'
@@ -201,7 +209,7 @@ task retrieve(type: Sync) {
         given:
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -224,14 +232,12 @@ task retrieve(type: Sync) {
 """
 
         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()
+        def uniqueVersionModule = mavenHttpRepo.module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenHttpRepo.module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
 
         and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo')
-        expectModuleServed(nonUniqueVersionModule, '/repo')
+        expectModuleServed(uniqueVersionModule)
+        expectModuleServed(nonUniqueVersionModule)
 
         and: "We resolve dependencies"
         run 'retrieve'
@@ -256,8 +262,8 @@ task retrieve(type: Sync) {
         file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
 
         when: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo', true, true)
-        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
+        expectChangedModuleServed(uniqueVersionModule)
+        expectChangedModuleServed(nonUniqueVersionModule)
 
         and: "Resolve dependencies with cache expired"
         executer.withArguments("-PnoTimeout")
@@ -274,7 +280,7 @@ task retrieve(type: Sync) {
 
         buildFile << """
 repositories {
-    maven { url "http://localhost:${server.port}/repo" }
+    maven { url "${mavenHttpRepo.uri}" }
 }
 
 configurations { compile }
@@ -294,11 +300,10 @@ task retrieve(type: Sync) {
 """
 
         when: "Publish the first snapshot"
-        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
-        module.publish()
+        def module = mavenHttpRepo.module("org.gradle", "testproject", "1.0-SNAPSHOT").publish()
 
         and: "Server handles requests"
-        expectModuleServed(module, '/repo')
+        expectModuleServed(module)
 
         and:
         run 'retrieve'
@@ -309,7 +314,7 @@ task retrieve(type: Sync) {
 
         when: "Server handles requests"
         server.resetExpectations()
-        expectChangedProbe('/repo', module, false)
+        expectChangedProbe(module)
 
         // Retrieve again with zero timeout should check for updated snapshot
         and:
@@ -323,15 +328,14 @@ task retrieve(type: Sync) {
     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()
+        def module = mavenHttpRepo.module("org.gradle", "testproject", "1.0-SNAPSHOT").publish()
 
         and:
         settingsFile << "include 'a', 'b'"
         buildFile << """
 allprojects {
     repositories {
-        maven { url "http://localhost:${server.port}/repo" }
+        maven { url "${mavenHttpRepo.uri}" }
     }
 
     configurations { compile }
@@ -351,7 +355,7 @@ allprojects {
 }
 """
         when: "Module is requested once"
-        expectModuleServed(module, '/repo')
+        expectModuleServed(module)
 
         then:
         run 'retrieve'
@@ -365,13 +369,13 @@ allprojects {
     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().publish()
+        def module = mavenHttpRepo("/repo", maven("repo1")).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        def module2 = mavenHttpRepo("/repo", maven("repo2")).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
         module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
         module2.pomFile << '    ' // ensure it's a different length to the first one
 
         and:
-        settingsFile << "include 'lock', 'resolve'"
+        settingsFile << "include 'first', 'second'"
         buildFile << """
 def fileLocks = [:]
 subprojects {
@@ -405,8 +409,9 @@ subprojects {
     }
     retrieve.dependsOn 'lock'
 }
-project('resolve') {
-    retrieve.dependsOn ':lock:retrieve'
+project('second') {
+    lock.dependsOn ':first:lock'
+    retrieve.dependsOn ':first:retrieve'
 
     task cleanup << {
         fileLocks.each { key, value ->
@@ -418,28 +423,26 @@ project('resolve') {
 }
 """
         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.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}.sha1", module2.sha1File(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.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}.sha1", module2.sha1File(module2.artifactFile))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
+        module.expectMetaDataGet()
+        module.expectPomGet()
+        module.expectMetaDataGet()
+        module.expectArtifactGet()
+
+        module2.expectMetaDataGet()
+        module2.expectPomHead()
+        module2.expectPomSha1Get()
+        module2.expectPomGet()
+        module2.expectMetaDataGet()
+        module2.expectArtifactHead()
+        module2.expectArtifactSha1Get()
+        module2.expectArtifactGet()
 
         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)
+        file('first/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
+        file('second/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
     }
 
     def "avoid redownload unchanged artifact when no checksum available"() {
@@ -448,7 +451,7 @@ project('resolve') {
         given:
         buildFile << """
             repositories {
-                maven { url "http://localhost:${server.port}/repo" }
+                maven { url "${mavenHttpRepo.uri}" }
             }
 
             configurations { compile }
@@ -468,29 +471,15 @@ project('resolve') {
         """
 
         and:
-        def module = mavenRepo().module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
+        def module = mavenHttpRepo.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.metaDataFile)
-        server.expectGet(pomPath, module.pomFile)
-        server.expectGet(metaDataPath, module.metaDataFile)
-        server.expectGet(jarPath, module.artifactFile)
+        expectModuleServed(module)
 
         run "retrieve"
 
@@ -499,15 +488,9 @@ project('resolve') {
         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.metaDataFile)
-        server.expectHead(pomPath, module.pomFile, originalPomLastMod, originalPomContentLength)
-        server.expectGet(metaDataPath, module.metaDataFile)
-        server.expectHead(jarPath, module.artifactFile, originalJarLastMod, originalJarContentLength)
+        expectChangedProbe(module)
 
         run "retrieve"
 
@@ -515,16 +498,17 @@ project('resolve') {
         downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
 
         when:
+        module.publishWithChangedContent()
         server.resetExpectations()
-        server.expectGet(metaDataPath, module.metaDataFile)
-        server.expectGetMissing(pomSha1Path)
-        server.expectHead(pomPath, module.pomFile)
-        server.expectGet(pomPath, module.pomFile)
-        server.expectGet(metaDataPath, module.metaDataFile)
-        server.expectGetMissing(jarSha1Path)
-        server.expectHead(jarPath, module.artifactFile)
-        server.expectGet(jarPath, module.artifactFile)
-
+        module.expectMetaDataGet()
+        module.expectPomHead()
+        module.expectPomSha1GetMissing()
+        module.expectPomGet()
+        // TODO - should only ask for metadata once
+        module.expectMetaDataGet()
+        module.expectArtifactHead()
+        module.expectArtifactSha1GetMissing()
+        module.expectArtifactGet()
 
         run "retrieve"
 
@@ -533,60 +517,49 @@ project('resolve') {
         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)
+    private expectModuleServed(MavenHttpModule module) {
+        module.expectMetaDataGet()
+        module.expectPomGet()
         // 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)
-        }
+        module.expectMetaDataGet()
+        module.expectArtifactGet()
     }
 
-    private expectChangedArtifactServed(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.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
-
+    private expectChangedModuleServed(MavenHttpModule module) {
+        module.expectMetaDataGet()
+        module.expectPomHead()
+        module.expectPomSha1Get()
+        module.expectPomGet()
         // 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.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+        module.expectMetaDataGet()
+        module.expectArtifactHead()
+        module.expectArtifactSha1Get()
+        module.expectArtifactGet()
     }
 
-    private expectChangedProbe(prefix, MavenModule module, boolean expectSha1) {
-        module.expectMetaDataGet(server, prefix)
-        module.expectPomHead(server, prefix)
-        if (expectSha1) {
-            module.expectPomSha1Get(server, prefix)
-        }
+    private expectChangedArtifactServed(MavenHttpModule module) {
+        module.expectMetaDataGet()
+        module.expectPomHead()
+        // TODO - should only ask for metadata once
+        module.expectMetaDataGet()
+        module.expectArtifactHead()
+        module.expectArtifactSha1Get()
+        module.expectArtifactGet()
+    }
 
-        module.expectMetaDataGet(server, prefix)
-        module.expectArtifactHead(server, prefix)
-        if (expectSha1) {
-            module.expectArtifactSha1Get(server, prefix)
-        }
+    private expectChangedProbe(MavenHttpModule module) {
+        module.expectMetaDataGet()
+        module.expectPomHead()
+        // TODO - should only ask for metadata once
+        module.expectMetaDataGet()
+        module.expectArtifactHead()
     }
     
-    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")
+    private expectModuleMissing(MavenHttpModule module) {
+        module.expectMetaDataGetMissing()
+        module.expectPomGetMissing()
         // 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")
+        module.expectMetaDataGetMissing()
+        module.expectArtifactHeadMissing()
     }
-
-
 }
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
index 5c8e233..30ca1bb 100644
--- 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
@@ -15,9 +15,8 @@
  */
 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.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
 class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
@@ -80,7 +79,7 @@ class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
     }
 
     def module(suffix) {
-        return new MavenRepository(apiAndImpl.dir.file("build/repo")).module("myorg", "apiAndImpl${suffix}", "1.0")
+        return maven(apiAndImpl.dir.file("build/repo")).module("myorg", "apiAndImpl${suffix}", "1.0")
     }
 
     def artifact(type) {
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
index bff4cca..aced1e3 100644
--- 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
@@ -15,9 +15,10 @@
  */
 package org.gradle.integtests.samples
 
-import org.junit.*
-
-import org.gradle.integtests.fixtures.*
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.UsesSample
+import org.junit.Rule
 
 class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec {
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
new file mode 100644
index 0000000..ce628c9
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.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.integtests.samples
+
+import org.gradle.api.JavaVersion
+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 spock.lang.IgnoreIf
+import spock.lang.Specification
+
+ at IgnoreIf({!JavaVersion.current().java6Compatible})
+class SamplesScalaZincIntegrationTest extends Specification {
+    @Rule GradleDistribution dist = new GradleDistribution()
+    @Rule GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule Sample sample = new Sample('scala/zinc')
+
+    def canBuildJar() {
+        given:
+        def projectDir = sample.dir
+
+        when:
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        then:
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/zinc.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+}
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
index dc9f5f2..c8ec5ca 100644
--- 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
@@ -93,7 +93,7 @@ task sayHearthyGoodbye << {
 
         //running web test then stopping jetty
         sample sample
-        def jettyStop = executer.withTasks('runTest', 'jettyStop').withArguments("-d").run()
+        def jettyStop = executer.withTasks('runTest', 'jettyStop').run()
 
         //test has completed
         assert jettyStop.output.contains('hello Gradle')
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
index f9f50ff..b9e591f 100755
--- 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
@@ -7,5 +7,5 @@
   <classpathentry kind="src" path="src/test/scala" output="build/classes/test"/>
   <classpathentry kind="lib" path="$cachePath/commons-collections/commons-collections/jars/commons-collections-3.2.jar"/>
   <classpathentry kind="lib" path="$cachePath/junit/junit/jars/junit-4.7.jar"/>
-  <classpathentry kind="lib" path="$cachePath/org.scala-lang/scala-library/jars/scala-library-2.8.1.jar"/>
+  <classpathentry kind="lib" path="$cachePath/org.scala-lang/scala-library/jars/scala-library-2.9.2.jar"/>
 </classpath>
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore
new file mode 100644
index 0000000..43cc577
Binary files /dev/null and b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/clientStore differ
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore
new file mode 100644
index 0000000..209c33e
Binary files /dev/null and b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest/shared/serverStore differ
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
deleted file mode 100644
index 0c3f64e..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
+++ /dev/null
@@ -1,80 +0,0 @@
-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/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/producer.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/producer.gradle
deleted file mode 100644
index 781de30..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/producer.gradle
+++ /dev/null
@@ -1,22 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'maven'
-
-group = 'org.gradle'
-version = '1.0-SNAPSHOT'
-
-def repoUrl = uri('repo')
-
-jar {
-    baseName = 'testproject'
-    if (project.hasProperty('emptyJar')) {
-        exclude '**/*'
-    }
-}
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: repoUrl)
-        }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/projectWithMavenSnapshots.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/projectWithMavenSnapshots.gradle
deleted file mode 100644
index acabcda..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/projectWithMavenSnapshots.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-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/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/src/main/java/org/gradle/Test.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/src/main/java/org/gradle/Test.java
deleted file mode 100644
index 9561bd6..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/src/main/java/org/gradle/Test.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package org.gradle;
-
-public class Test {
-}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle b/subprojects/internal-integ-testing/internal-integ-testing.gradle
index ea5a522..61cd919 100644
--- a/subprojects/internal-integ-testing/internal-integ-testing.gradle
+++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle
@@ -13,8 +13,6 @@
  * 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")
@@ -30,6 +28,7 @@ dependencies {
 }
 
 useTestFixtures(sourceSet: 'main')
+useClassycle()
 
 task prepareVersionsInfo(type: PrepareVersionsInfo) {
    url = "http://services.gradle.org/versions/all"
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
index 4dda18a..24de52a 100644
--- 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
@@ -32,6 +32,10 @@ public abstract class AbstractDelegatingGradleExecuter extends AbstractGradleExe
         return configureExecuter().getDaemonRegistry();
     }
 
+    public void assertCanExecute() throws AssertionError {
+        configureExecuter().assertCanExecute();
+    }
+
     @Override
     public GradleHandle doStart() {
         return configureExecuter().start();
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
index 81f33b9..1acb363 100644
--- 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
@@ -15,7 +15,11 @@
  */
 package org.gradle.integtests.fixtures;
 
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.internal.jvm.Jvm;
+import org.gradle.listener.ActionBroadcast;
 import org.gradle.util.TextUtil;
 
 import java.io.ByteArrayInputStream;
@@ -27,6 +31,15 @@ import java.util.*;
 import static java.util.Arrays.asList;
 
 public abstract class AbstractGradleExecuter implements GradleExecuter {
+
+    // If no explicit timeout is specified, this will be used.
+    // This is to avoid daemons “accidentally” launched hanging around for a long time.
+    private static final int DEFAULT_DAEMON_IDLE_TIMEOUT_SECS = 10;
+
+    // Specified in the build config to point under /build
+    // This is primarily to avoid filling ~/.gradle on CI builds
+    private static final String DEFAULT_DAEMON_REGISTRY_DIR_PROPERTY = "org.gradle.integtest.daemon.registry";
+
     private final List<String> args = new ArrayList<String>();
     private final List<String> tasks = new ArrayList<String>();
     private File workingDir;
@@ -37,6 +50,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private Map<String, String> environmentVars = new HashMap<String, String>();
     private List<File> initScripts = new ArrayList<File>();
     private String executable;
+    private File gradleUserHomeDir;
     private File userHomeDir;
     private File javaHome;
     private File buildScript;
@@ -44,8 +58,12 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private File settingsFile;
     private InputStream stdin;
     private String defaultCharacterEncoding;
+    private Integer daemonIdleTimeoutSecs;
+    private File daemonBaseDir;
     //gradle opts make sense only for forking executer but having them here makes more sense
     protected final List<String> gradleOpts = new ArrayList<String>();
+    protected boolean noDefaultJvmArgs;
+    private final ActionBroadcast<GradleExecuter> beforeExecute = new ActionBroadcast<GradleExecuter>();
 
     public GradleExecuter reset() {
         args.clear();
@@ -60,14 +78,25 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         dependencyList = false;
         searchUpwards = false;
         executable = null;
-        userHomeDir = null;
+        gradleUserHomeDir = null;
         javaHome = null;
         environmentVars.clear();
         stdin = null;
         defaultCharacterEncoding = null;
+        daemonIdleTimeoutSecs = null;
+        daemonBaseDir = null;
+        noDefaultJvmArgs = false;
         return this;
     }
 
+    public void beforeExecute(Action<? super GradleExecuter> action) {
+        beforeExecute.add(action);
+    }
+
+    public void beforeExecute(Closure action) {
+        beforeExecute.add(new ClosureBackedAction<GradleExecuter>(action));
+    }
+
     public GradleExecuter inDirectory(File directory) {
         workingDir = directory;
         return this;
@@ -109,7 +138,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         if (dependencyList) {
             executer.withDependencyList();
         }
-        executer.withUserHomeDir(userHomeDir);
+        executer.withGradleUserHomeDir(gradleUserHomeDir);
+        if (userHomeDir != null) {
+            executer.withUserHomeDir(userHomeDir);
+        }
         if (stdin != null) {
             executer.withStdIn(stdin);
         }
@@ -117,6 +149,15 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
             executer.withDefaultCharacterEncoding(defaultCharacterEncoding);
         }
         executer.withGradleOpts(gradleOpts.toArray(new String[gradleOpts.size()]));
+        if (daemonIdleTimeoutSecs != null) {
+            executer.withDaemonIdleTimeoutSecs(daemonIdleTimeoutSecs);
+        }
+        if (daemonBaseDir != null) {
+            executer.withDaemonBaseDir(daemonBaseDir);
+        }
+        if (noDefaultJvmArgs) {
+            executer.withNoDefaultJvmArgs();
+        }
     }
 
     public GradleExecuter usingBuildScript(File buildScript) {
@@ -124,10 +165,6 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
-    public File getBuildScript() {
-        return buildScript;
-    }
-
     public GradleExecuter usingProjectDirectory(File projectDir) {
         this.projectDir = projectDir;
         return this;
@@ -138,15 +175,16 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
-    public File getSettingsFile() {
-        return settingsFile;
-    }
-
     public GradleExecuter usingInitScript(File initScript) {
         initScripts.add(initScript);
         return this;
     }
 
+    public GradleExecuter withGradleUserHomeDir(File userHomeDir) {
+        this.gradleUserHomeDir = userHomeDir;
+        return this;
+    }
+
     public File getUserHomeDir() {
         return userHomeDir;
     }
@@ -231,6 +269,11 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
+    public GradleExecuter withArgument(String arg) {
+        this.args.add(arg);
+        return this;
+    }
+
     public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
         environmentVars.clear();
         for (Map.Entry<String, ?> entry : environment.entrySet()) {
@@ -257,6 +300,29 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
+    public GradleExecuter withDaemonIdleTimeoutSecs(int secs) {
+        daemonIdleTimeoutSecs = secs;
+        return this;
+    }
+
+    public GradleExecuter withNoDefaultJvmArgs() {
+        noDefaultJvmArgs = true;
+        return this;
+    }
+
+    protected Integer getDaemonIdleTimeoutSecs() {
+        return daemonIdleTimeoutSecs;
+    }
+
+    public GradleExecuter withDaemonBaseDir(File daemonBaseDir) {
+        this.daemonBaseDir = daemonBaseDir;
+        return this;
+    }
+
+    protected File getDaemonBaseDir() {
+        return daemonBaseDir;
+    }
+
     protected List<String> getAllArgs() {
         List<String> allArgs = new ArrayList<String>();
         if (buildScript != null) {
@@ -287,16 +353,34 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         if (!searchUpwards) {
             allArgs.add("--no-search-upward");
         }
-        if (userHomeDir != null) {
+        if (gradleUserHomeDir != null) {
             allArgs.add("--gradle-user-home");
-            allArgs.add(userHomeDir.getAbsolutePath());
+            allArgs.add(gradleUserHomeDir.getAbsolutePath());
+        }
+
+        // Prevent from running with the default idle timeout as it causes CI chaos
+        int effectiveDaemonIdleTimeoutSecs = daemonIdleTimeoutSecs == null ? DEFAULT_DAEMON_IDLE_TIMEOUT_SECS : daemonIdleTimeoutSecs;
+        allArgs.add("-Dorg.gradle.daemon.idletimeout=" + effectiveDaemonIdleTimeoutSecs * 1000);
+
+        // Prevent from running with the default daemon dir (~/.gradle/daemon) as it fills up on the CI server
+        String effectiveDaemonBaseDir = null;
+        if (daemonBaseDir == null) {
+            effectiveDaemonBaseDir = System.getProperty(DEFAULT_DAEMON_REGISTRY_DIR_PROPERTY);
+        } else {
+            effectiveDaemonBaseDir = daemonBaseDir.getAbsolutePath();
+        }
+        if (effectiveDaemonBaseDir != null) {
+            args.add("-Dorg.gradle.daemon.registry.base=" + effectiveDaemonBaseDir);
         }
+
         allArgs.addAll(args);
         allArgs.addAll(tasks);
         return allArgs;
     }
 
     public final GradleHandle start() {
+        fireBeforeExecute();
+        assertCanExecute();
         try {
             return doStart();
         } finally {
@@ -305,6 +389,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     }
 
     public final ExecutionResult run() {
+        fireBeforeExecute();
+        assertCanExecute();
         try {
             return doRun();
         } finally {
@@ -313,6 +399,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     }
 
     public final ExecutionFailure runWithFailure() {
+        fireBeforeExecute();
+        assertCanExecute();
         try {
             return doRunWithFailure();
         } finally {
@@ -320,6 +408,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         }
     }
 
+    private void fireBeforeExecute() {
+        beforeExecute.execute(this);
+    }
+
     protected GradleHandle doStart() {
         throw new UnsupportedOperationException(String.format("%s does not support running asynchronously.", getClass().getSimpleName()));
     }
@@ -334,7 +426,5 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     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
index b33af72..e14f61d 100644
--- 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.integtests.fixtures
 
+import org.gradle.test.fixtures.ivy.IvyFileRepository
 import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
@@ -31,6 +32,8 @@ class AbstractIntegrationSpec extends Specification {
 
     ExecutionResult result
     ExecutionFailure failure
+    private MavenFileRepository mavenRepo
+    private IvyFileRepository ivyRepo
 
     protected TestFile getBuildFile() {
         testDir.file('build.gradle')
@@ -53,13 +56,17 @@ class AbstractIntegrationSpec extends Specification {
     }
 
     protected GradleExecuter inDirectory(String path) {
-        inDirectory(testDir.file(path))
+        inDirectory(file(path))
     }
 
     protected GradleExecuter inDirectory(File directory) {
         executer.inDirectory(directory);
     }
 
+    protected GradleExecuter projectDir(path) {
+        executer.usingProjectDirectory(file(path))
+    }
+
     protected GradleDistribution requireOwnUserHomeDir() {
         distribution.requireOwnUserHomeDir()
         distribution
@@ -72,14 +79,15 @@ class AbstractIntegrationSpec extends Specification {
         succeeds(*tasks)
     }
 
+    protected GradleExecuter args(String... args) {
+        executer.withArguments(args)
+    }
+
     protected GradleExecuter withDebugLogging() {
         executer.withArguments("-d")
     }
 
     protected ExecutionResult succeeds(String... tasks) {
-        if (settingsFile.exists()) {
-            executer.usingSettingsFile(settingsFile)
-        }
         result = executer.withTasks(*tasks).run()
     }
 
@@ -130,11 +138,40 @@ class AbstractIntegrationSpec extends Specification {
     }
 
     ArtifactBuilder artifactBuilder() {
-        def executer = new InProcessGradleExecuter()
-        executer.withUserHomeDir(distribution.getUserHomeDir())
+        def executer = distribution.executer()
+        executer.withGradleUserHomeDir(distribution.getUserHomeDir())
         return new GradleBackedArtifactBuilder(executer, getTestDir().file("artifacts"))
     }
 
+    public MavenFileRepository maven(TestFile repo) {
+        return new MavenFileRepository(repo)
+    }
+
+    public MavenFileRepository maven(Object repo) {
+        return new MavenFileRepository(file(repo))
+    }
+
+    public MavenFileRepository getMavenRepo() {
+        if (mavenRepo == null) {
+            mavenRepo = new MavenFileRepository(file("maven-repo"))
+        }
+        return mavenRepo
+    }
+
+    public IvyFileRepository ivy(TestFile repo) {
+        return new IvyFileRepository(repo)
+    }
+
+    public IvyFileRepository ivy(Object repo) {
+        return new IvyFileRepository(file(repo))
+    }
+
+    public IvyFileRepository getIvyRepo() {
+        if (ivyRepo == null) {
+            ivyRepo = new IvyFileRepository(file("ivy-repo"))
+        }
+        return ivyRepo
+    }
 
     def createZip(String name, Closure cl) {
         TestFile zipRoot = file("${name}.root")
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
index 3fa108c..742ddeb 100644
--- 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
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.test.fixtures.ivy.IvyFileRepository;
+import org.gradle.test.fixtures.ivy.IvyRepository;
 import org.gradle.util.TestFile;
 import org.gradle.util.TestFileContext;
 import org.junit.Rule;
@@ -25,6 +27,9 @@ public abstract class AbstractIntegrationTest implements TestFileContext {
     @Rule public final GradleDistribution distribution = new GradleDistribution();
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
 
+    private MavenFileRepository mavenRepo;
+    private IvyFileRepository ivyRepo;
+
     public TestFile getTestDir() {
         return distribution.getTestDir();
     }
@@ -54,12 +59,38 @@ public abstract class AbstractIntegrationTest implements TestFileContext {
     }
 
     protected ArtifactBuilder artifactBuilder() {
-        InProcessGradleExecuter gradleExecuter = new InProcessGradleExecuter();
-        gradleExecuter.withUserHomeDir(distribution.getUserHomeDir());
+        GradleDistributionExecuter gradleExecuter = distribution.executer();
+        gradleExecuter.withGradleUserHomeDir(distribution.getUserHomeDir());
         return new GradleBackedArtifactBuilder(gradleExecuter, getTestDir().file("artifacts"));
     }
 
     public MavenRepository maven(TestFile repo) {
-        return new MavenRepository(repo);
+        return new MavenFileRepository(repo);
+    }
+
+    public MavenRepository maven(Object repo) {
+        return new MavenFileRepository(file(repo));
+    }
+
+    public MavenRepository getMavenRepo() {
+        if (mavenRepo == null) {
+            mavenRepo = new MavenFileRepository(file("maven-repo"));
+        }
+        return mavenRepo;
+    }
+
+    public IvyRepository ivy(TestFile repo) {
+        return new IvyFileRepository(repo);
+    }
+
+    public IvyRepository ivy(Object repo) {
+        return new IvyFileRepository(file(repo));
+    }
+
+    public IvyRepository getIvyRepo() {
+        if (ivyRepo == null) {
+            ivyRepo = new IvyFileRepository(file("ivy-repo"));
+        }
+        return ivyRepo;
     }
 }
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
index be95f83..d02941f 100755
--- 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
@@ -19,6 +19,7 @@ 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.manipulation.*;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
 import org.junit.runner.notification.RunNotifier;
@@ -32,7 +33,7 @@ import java.util.*;
 /**
  * A base class for those test runners which execute a test multiple times.
  */
-public abstract class AbstractMultiTestRunner extends Runner {
+public abstract class AbstractMultiTestRunner extends Runner implements Filterable, Sortable {
     protected final Class<?> target;
     private Description description;
     private final List<Execution> executions = new ArrayList<Execution>();
@@ -43,36 +44,64 @@ public abstract class AbstractMultiTestRunner extends Runner {
 
     @Override
     public Description getDescription() {
-        init();
+        initDescription();
         return description;
     }
 
     @Override
     public void run(RunNotifier notifier) {
-        init();
+        initDescription();
         for (Execution execution : executions) {
             execution.run(notifier);
         }
     }
 
-    private void init() {
-        if (description == null) {
+    public void filter(Filter filter) throws NoTestsRemainException {
+        initExecutions();
+        for (Execution execution : executions) {
+            execution.filter(filter);
+        }
+        invalidateDescription();
+    }
+
+    public void sort(Sorter sorter) {
+        initExecutions();
+        for (Execution execution : executions) {
+            execution.sort(sorter);
+        }
+        invalidateDescription();
+    }
+
+    private void initExecutions() {
+        if (executions.isEmpty()) {
             createExecutions();
-            description = Description.createSuiteDescription(target);
             for (Execution execution : executions) {
                 execution.init(target);
+            }
+        }
+    }
+
+    private void initDescription() {
+        initExecutions();
+        if (description == null) {
+            description = Description.createSuiteDescription(target);
+            for (Execution execution : executions) {
                 execution.addDescriptions(description);
             }
         }
     }
 
+    private void invalidateDescription() {
+        description = null;
+    }
+
     protected abstract void createExecutions();
 
     protected void add(Execution execution) {
         executions.add(execution);
     }
 
-    protected static abstract class Execution {
+    protected static abstract class Execution implements Sortable, Filterable {
         private Runner runner;
         protected Class<?> target;
         private final Map<Description, Description> descriptionTranslations = new HashMap<Description, Description>();
@@ -163,6 +192,18 @@ public abstract class AbstractMultiTestRunner extends Runner {
             }
         }
 
+        public void filter(Filter filter) throws NoTestsRemainException {
+            if (runner instanceof Filterable) {
+                ((Filterable) runner).filter(filter);
+            }
+        }
+
+        public void sort(Sorter sorter) {
+            if (runner instanceof Sortable) {
+                ((Sortable) runner).sort(sorter);
+            }
+        }
+
         protected void before() {
         }
 
@@ -184,7 +225,7 @@ public abstract class AbstractMultiTestRunner extends Runner {
         }
 
         /**
-         * Returns a display name for this execution. Used in the Junit descriptions for test execution.
+         * Returns a display name for this execution. Used in the JUnit descriptions for test execution.
          */
         protected abstract String getDisplayName();
 
@@ -199,7 +240,7 @@ public abstract class AbstractMultiTestRunner extends Runner {
          * 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);
+            return Collections.singletonList(target);
         }
     }
 }
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
old mode 100644
new mode 100755
index d975d66..8d0308f
--- 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
@@ -20,6 +20,8 @@ import org.gradle.internal.os.OperatingSystem;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows the tests to get hold of an alternative Java installation when needed.
@@ -31,6 +33,9 @@ abstract public class AvailableJavaHomes {
         return value == null ? null : GFileUtils.canonicalise(new File(value));
     }
 
+    /**
+     * Locates a JVM installation that is different to the current JVM.
+     */
     public static File getBestAlternative() {
         Jvm jvm = Jvm.current();
 
@@ -68,15 +73,23 @@ abstract public class AvailableJavaHomes {
             }
         } 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.getJavaVersion().isJava6() && !file.getName().contains("1.6")) {
-                        return file;
-                    }
-                    if (jvm.getJavaVersion().isJava7() && !file.getName().contains("1.7")) {
-                        return file;
+            List<File> installDirs = new ArrayList<File>();
+            File candidate = new File("c:/Program Files/Java");
+            if (candidate.isDirectory()) {
+                installDirs.add(candidate);
+            }
+            // Attempt to look for 32-bit version under 64-bit OS
+            candidate = new File("c:/Program Files (x86)/Java");
+            if (candidate.isDirectory()) {
+                installDirs.add(candidate);
+            }
+            for (File installDir : installDirs) {
+                for (File file : installDir.listFiles()) {
+                    if (file.getName().startsWith("jdk")) {
+                        javaHome = GFileUtils.canonicalise(file);
+                        if (!javaHome.equals(jvm.getJavaHome()) && javaHome.isDirectory() && new File(javaHome, "bin/java.exe").isFile()) {
+                            return javaHome;
+                        }
                     }
                 }
             }
@@ -85,71 +98,36 @@ abstract public class AvailableJavaHomes {
         return null;
     }
 
-    public static File getBestJreAlternative() {
+    /**
+     * Locates a JRE installation for the current JVM. Prefers a stand-alone JRE installation over one that is part of a JDK install.
+     *
+     * @return The JRE home directory, or null if not found
+     */
+    public static File getBestJre() {
         Jvm jvm = Jvm.current();
+        File jreHome;
 
-        // Use environment variables
-        File jreHome = null;
-        if (jvm.getJavaVersion().isJava6Compatible()) {
-            jreHome = firstAvailableJRE("15", "17");
-        } else if (jvm.getJavaVersion().isJava5Compatible()) {
-            jreHome = firstAvailableJRE();
-        }
-        if (jreHome != null) {
-            return jreHome;
-        }
-
-        if (OperatingSystem.current().isMacOsX()) {
-            File registeredJvms = new File("/Library/Java/JavaVirtualMachines");
-            if (registeredJvms.isDirectory()) {
-                for (File candidate : registeredJvms.listFiles()) {
-                    jreHome = GFileUtils.canonicalise(new File(candidate, "Contents/Home/jre"));
-                    if (!jreHome.equals(jvm.getJavaHome()) && jreHome.isDirectory() && new File(jreHome, "bin/java").isFile()) {
-                        return jreHome;
-                    }
-                }
-            }
-        } else if (OperatingSystem.current().isLinux()) {
-            // Ubuntu specific
-            File installedJvms = new File("/usr/lib/jvm");
-            if (installedJvms.isDirectory()) {
-                for (File candidate : installedJvms.listFiles()) {
-                    jreHome = new File(GFileUtils.canonicalise(candidate), "jre");
-                    if (!jreHome.equals(jvm.getJavaHome()) && jreHome.isDirectory() && new File(jreHome, "bin/java").isFile()) {
-                        return jreHome;
-                    }
+        if (OperatingSystem.current().isWindows()) {
+            if (jvm.getJavaVersion().isJava6()) {
+                jreHome = new File(jvm.getJavaHome().getParentFile(), "jre6");
+                if (jreHome.isDirectory() && new File(jreHome, "bin/java.exe").isFile()) {
+                    return jreHome;
                 }
             }
-        } 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("jre")) {
-                    if (jvm.getJavaVersion().isJava6() && !file.getName().contains("1.6")) {
-                        return file;
-                    }
-                    if (jvm.getJavaVersion().isJava7() && !file.getName().contains("1.7")) {
-                        return file;
-                    }
+            if (jvm.getJavaVersion().isJava7()) {
+                jreHome = new File(jvm.getJavaHome().getParentFile(), "jre7");
+                if (jreHome.isDirectory() && new File(jreHome, "bin/java.exe").isFile()) {
+                    return jreHome;
                 }
             }
         }
-        return null;
-    }
-
-    private static File firstAvailableJRE(String... labels) {
-        File javaHome = firstAvailable(labels);
-        if (javaHome != null) {
-            final File jre = new File(javaHome, "jre");
-            if (jre.isDirectory()) {
-                return jre;
-            }
+        jreHome = new File(jvm.getJavaHome(), "jre");
+        if (jreHome.isDirectory()) {
+            return jreHome;
         }
         return null;
     }
 
-
     public static File firstAvailable(String... labels) {
         for (String label : labels) {
             File found = getJavaHome(label);
@@ -159,4 +137,4 @@ abstract public class AvailableJavaHomes {
         }
         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
index 7b1cc51..b3331a4 100644
--- 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
@@ -72,6 +72,11 @@ public interface BasicGradleDistribution {
     boolean isOpenApiSupported();
 
     /**
+     * Returns true if the cache implementation in this distribution is multi-process safe.
+     */
+    boolean isMultiProcessSafeCache();
+
+    /**
      * 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/ClassFile.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ClassFile.groovy
new file mode 100644
index 0000000..53fd66b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ClassFile.groovy
@@ -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.integtests.fixtures
+
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.Opcodes
+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 MethodVisitor(Opcodes.ASM4) {
+            @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 ClassVisitor(Opcodes.ASM4) {
+            @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/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
index 21c4b16..4e1efdc 100755
--- 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
@@ -24,6 +24,7 @@ import spock.lang.Specification
 abstract class CrossVersionIntegrationSpec extends Specification {
     @Rule public final GradleDistribution current = new GradleDistribution()
     static BasicGradleDistribution previous
+    private MavenFileRepository mavenRepo
 
     BasicGradleDistribution getPrevious() {
         return previous
@@ -41,11 +42,23 @@ abstract class CrossVersionIntegrationSpec extends Specification {
         testDir.file(path);
     }
 
+    protected MavenRepository getMavenRepo() {
+        if (mavenRepo == null) {
+            mavenRepo = new MavenFileRepository(file("maven-repo"))
+        }
+        return mavenRepo
+    }
+
     def version(BasicGradleDistribution dist) {
         def executer = dist.executer();
         if (executer instanceof GradleDistributionExecuter) {
             executer.withDeprecationChecksDisabled()
         }
+        if (dist.multiProcessSafeCache) {
+            executer.withGradleUserHomeDir(current.userHomeDir)
+        } else {
+            executer.withGradleUserHomeDir(current.file("user-home/$dist.version"))
+        }
         executer.inDirectory(testDir)
         return executer;
     }
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
index fa056ba..024482f 100644
--- 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
@@ -16,25 +16,21 @@
 package org.gradle.integtests.fixtures;
 
 import org.apache.commons.collections.CollectionUtils;
+import org.gradle.api.JavaVersion;
 
-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;
+class DaemonGradleExecuter extends ForkingGradleExecuter {
     private final boolean allowExtraLogging;
+    private final boolean noDefaultJvmArgs;
 
-    public DaemonGradleExecuter(GradleDistribution distribution, File daemonBaseDir, boolean allowExtraLogging) {
+    public DaemonGradleExecuter(GradleDistribution distribution, boolean allowExtraLogging, boolean noDefaultJvmArgs) {
         super(distribution.getGradleHomeDir());
-        this.distribution = distribution;
-        this.daemonBaseDir = daemonBaseDir;
         this.allowExtraLogging = allowExtraLogging;
+        this.noDefaultJvmArgs = noDefaultJvmArgs;
     }
 
     @Override
@@ -45,25 +41,13 @@ public class
         args.add("--daemon");
 
         args.addAll(originalArgs);
+        configureJvmArgs(args);
+        configureDefaultLogging(args);
 
-        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);
+        if (getUserHomeDir() != null) {
+            args.add(String.format("-Duser.home=%s", getUserHomeDir().getPath()));
         }
 
-        configureDefaultLogging(args);
-
         return args;
     }
 
@@ -78,25 +62,13 @@ public class
         }
     }
 
-    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");
+    private void configureJvmArgs(List<String> args) {
+        if(!noDefaultJvmArgs) {
+            String jvmArgs  = "-Dorg.gradle.jvmargs=-ea -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError";
+            if (JavaVersion.current().isJava5()) {
+                jvmArgs = String.format("%s -XX:+CMSPermGenSweepingEnabled -Dcom.sun.management.jmxremote", jvmArgs);
+            }
+            args.add(0, jvmArgs);
         }
     }
-
-    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
index 5299d4d..9d62926 100644
--- 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
@@ -31,7 +31,7 @@ import org.gradle.logging.internal.StreamBackedStandardOutputListener;
 
 import java.lang.management.ManagementFactory;
 
-public class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
+class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
 
     private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), false);
 
@@ -39,6 +39,9 @@ public class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
         return daemonClientServices.get(DaemonRegistry.class);
     }
 
+    public void assertCanExecute() throws AssertionError {
+    }
+
     protected ExecutionResult doRun() {
         return doRun(false);
     }
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
index 9a41173..380ee52 100644
--- 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
@@ -23,6 +23,8 @@ public interface ExecutionResult {
 
     String getError();
 
+    ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines);
+
     /**
      * Returns the tasks have been executed in order (includes tasks that were skipped). Note: ignores buildSrc tasks.
      */
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
index aca014a..4de7f1a 100644
--- 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
@@ -16,13 +16,9 @@
 
 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;
@@ -32,13 +28,11 @@ 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 {
+class ForkingGradleExecuter extends AbstractGradleExecuter {
     private static final Logger LOG = LoggerFactory.getLogger(ForkingGradleExecuter.class);
     private final TestFile gradleHomeDir;
 
@@ -50,35 +44,12 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
 //        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()));
+        return new DaemonRegistryServices(getDaemonBaseDir()).get(DaemonRegistry.class);
     }
 
-    /**
-     * Adds some options to the GRADLE_OPTS environment variable to use.
-     */
-    public void addGradleOpts(String... opts) {
-        gradleOpts.addAll(Arrays.asList(opts));
+    public void assertCanExecute() throws AssertionError {
+        // Can run any build
     }
 
     @Override
@@ -128,13 +99,17 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
 
     @Override
     public GradleHandle doStart() {
-        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
+        return createGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
             public ExecHandleBuilder create() {
                 return createExecHandleBuilder();
             }
         }).start();
     }
 
+    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ForkingGradleHandle(encoding, execHandleFactory);
+    }
+
     protected ExecutionResult doRun() {
         return start().waitForFinish();
     }
@@ -144,6 +119,10 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
     }
 
     private String formatGradleOpts() {
+        if (getUserHomeDir() != null) {
+            gradleOpts.add(String.format("-Duser.home=%s", getUserHomeDir()));
+        }
+
         StringBuilder result = new StringBuilder();
         for (String gradleOpt : gradleOpts) {
             if (result.length() > 0) {
@@ -158,7 +137,7 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
                 result.append(gradleOpt);
             }
         }
-        
+
         result.append(" -Dfile.encoding=");
         result.append(getDefaultCharacterEncoding());
         result.append(" -Dorg.gradle.deprecation.trace=true");
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
index 94e0f4c..5e066b4 100644
--- 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
@@ -107,8 +107,8 @@ class ForkingGradleHandle extends OutputScrapingGradleHandle {
 
         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);
+            String message = String.format("Gradle execution %s in %s with: %s %s%nOutput:%n%s%n-----%nError:%n%s%n-----%n",
+                    expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), execHandle.getArguments(), output, error);
             throw new UnexpectedBuildFailure(message);
         }
         return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
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
index bf90ce0..41a4bee 100644
--- 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
@@ -39,22 +39,22 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
     private static final TestFile USER_GUIDE_INFO_DIR;
     private static final TestFile DISTS_DIR;
     private static final TestFile LIBS_REPO;
+    private static final TestFile DAEMON_BASE_DIR;
 
     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");
+        SAMPLES_DIR = file("integTest.samplesdir", String.format("%s/samples", GRADLE_HOME_DIR));
         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");
+        DAEMON_BASE_DIR = file("org.gradle.integtest.daemon.registry", "build/daemon");
     }
 
     public GradleDistribution() {
@@ -91,17 +91,24 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
         return true;
     }
 
+    public boolean isMultiProcessSafeCache() {
+        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 TestFile getDaemonBaseDir() {
+        if (usingIsolatedDaemons) {
+            return getTestDir().file("daemon");
+        } else {
+            return DAEMON_BASE_DIR;
+        }
     }
 
     public void requireOwnUserHomeDir() {
-        usingOwnUserHomeDir = true;
         userHome = getTestDir().file("user-home");
     }
 
@@ -110,7 +117,6 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
     }
 
     public void requireIsolatedDaemons() {
-        requireOwnUserHomeDir();
         this.usingIsolatedDaemons = true;
     }
 
@@ -231,17 +237,5 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
     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
index 16ba09b..706bc5a 100644
--- 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
@@ -39,27 +39,35 @@ import static org.gradle.util.Matchers.matchesRegexp;
  */
 public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter implements MethodRule {
     private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
+    private static final String UNKNOWN_OS_SYS_PROP = "org.gradle.integtest.unknownos";
+    private static final int DEFAULT_DAEMON_IDLE_TIMEOUT_SECS = 2 * 60;
 
     private GradleDistribution dist;
     private boolean workingDirSet;
-    private boolean userHomeSet;
+    private boolean gradleUserHomeDirSet;
     private boolean deprecationChecksOn = true;
     private boolean stackTraceChecksOn = true;
     private Executer executerType;
-    private File daemonBaseDir;
+
     private boolean allowExtraLogging = true;
-    private boolean mustFork;
 
     public enum Executer {
         embedded(false),
         forking(true),
         daemon(true),
-        embeddedDaemon(false);
+        embeddedDaemon(false),
+        parallel(true, true);
 
         final public boolean forks;
+        final public boolean executeParallel;
 
         Executer(boolean forks) {
+            this(forks, false);
+        }
+
+        Executer(boolean forks, boolean parallel) {
             this.forks = forks;
+            this.executeParallel = parallel;
         }
     }
 
@@ -85,22 +93,11 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         reset();
     }
 
-    public boolean isMustFork() {
-        return mustFork;
-    }
-
-    public void setMustFork(boolean mustFork) {
-        this.mustFork = mustFork;
-    }
-
-    public Executer getType() {
-        return executerType;
-    }
-
     public Statement apply(Statement base, final FrameworkMethod method, Object target) {
         if (dist == null) {
             dist = RuleHelper.getField(target, GradleDistribution.class);
         }
+        beforeExecute(new RedirectMavenCentral(dist.getTemporaryFolder()));
         return base;
     }
 
@@ -108,10 +105,9 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
     public GradleDistributionExecuter reset() {
         super.reset();
         workingDirSet = false;
-        userHomeSet = false;
+        gradleUserHomeDirSet = false;
         deprecationChecksOn = true;
         stackTraceChecksOn = true;
-        mustFork = false;
         DeprecationLogger.reset();
         return this;
     }
@@ -124,21 +120,16 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
     }
 
     @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;
+    public GradleDistributionExecuter withGradleUserHomeDir(File userHomeDir) {
+        super.withGradleUserHomeDir(userHomeDir);
+        gradleUserHomeDirSet = true;
         return this;
     }
 
     public GradleDistributionExecuter withDeprecationChecksDisabled() {
         deprecationChecksOn = false;
+        // turn off stack traces too
+        stackTraceChecksOn = false;
         return this;
     }
 
@@ -187,7 +178,7 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         String error = result.getError();
         if (result instanceof ExecutionFailure) {
             // Axe everything after the expected exception
-            int pos = error.lastIndexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
+            int pos = error.indexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
             if (pos >= 0) {
                 error = error.substring(0, pos);
             }
@@ -209,7 +200,7 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
     }
 
     private void assertNoStackTraces(String output, String displayName) {
-        if (containsLine(matchesRegexp("\\s+at [\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
+        if (containsLine(matchesRegexp("\\s+(at\\s+)?[\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
             throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
         }
     }
@@ -227,40 +218,71 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         if (!workingDirSet) {
             inDirectory(dist.getTestDir());
         }
-        if (!userHomeSet) {
-            withUserHomeDir(dist.getUserHomeDir());
+        if (!gradleUserHomeDirSet) {
+            withGradleUserHomeDir(dist.getUserHomeDir());
+        }
+        if (getDaemonIdleTimeoutSecs() == null) {
+            if (dist.isUsingIsolatedDaemons() || getDaemonBaseDir() != null) {
+                withDaemonIdleTimeoutSecs(20);
+            } else {
+                withDaemonIdleTimeoutSecs(DEFAULT_DAEMON_IDLE_TIMEOUT_SECS);
+            }
+        }
+        if (getDaemonBaseDir() == null) {
+            withDaemonBaseDir(dist.getDaemonBaseDir());
         }
 
         if (!getClass().desiredAssertionStatus()) {
             throw new RuntimeException("Assertions must be enabled when running integration tests.");
         }
 
-        InProcessGradleExecuter inProcessGradleExecuter = new InProcessGradleExecuter();
-        copyTo(inProcessGradleExecuter);
+        GradleExecuter gradleExecuter = createExecuter(executerType);
+        configureExecuter(gradleExecuter);
+        try {
+            gradleExecuter.assertCanExecute();
+        } catch (AssertionError assertionError) {
+            gradleExecuter = new ForkingGradleExecuter(dist.getGradleHomeDir());
+            configureExecuter(gradleExecuter);
+        }
 
-        GradleExecuter returnedExecuter = inProcessGradleExecuter;
+        return gradleExecuter;
+    }
 
-        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());
+    private void configureExecuter(GradleExecuter gradleExecuter) {
+        copyTo(gradleExecuter);
+
+        configureTmpDir(gradleExecuter);
+        configureForSettingsFile(gradleExecuter);
+
+        if (System.getProperty(UNKNOWN_OS_SYS_PROP) != null) {
+            gradleExecuter.withGradleOpts("-Dos.arch=unknown architecture", "-Dos.name=unknown operating system", "-Dos.version=unknown version");
         }
+    }
 
-        if (executerType == Executer.embeddedDaemon) {
-            GradleExecuter embeddedDaemonExecutor = new EmbeddedDaemonGradleExecuter();
-            copyTo(embeddedDaemonExecutor);
-            returnedExecuter = embeddedDaemonExecutor;
+    private GradleExecuter createExecuter(Executer executerType) {
+        switch (executerType) {
+            case embeddedDaemon:
+                return new EmbeddedDaemonGradleExecuter();
+            case embedded:
+                return new InProcessGradleExecuter();
+            case daemon:
+                return new DaemonGradleExecuter(dist, !isQuiet() && allowExtraLogging, noDefaultJvmArgs);
+            case parallel:
+                return new ParallelForkingGradleExecuter(dist.getGradleHomeDir());
+            case forking:
+                return new ForkingGradleExecuter(dist.getGradleHomeDir());
+            default:
+                throw new RuntimeException("Not a supported executer type: " + executerType);
         }
+    }
 
+    private void configureTmpDir(GradleExecuter gradleExecuter) {
+        TestFile tmpDir = getTmpDir();
+        tmpDir.createDir();
+        gradleExecuter.withGradleOpts(String.format("-Djava.io.tmpdir=%s", tmpDir));
+    }
+
+    private void configureForSettingsFile(GradleExecuter gradleExecuter) {
         boolean settingsFound = false;
         for (
                 TestFile dir = new TestFile(getWorkingDir()); dir != null && dist.isFileUnderTest(dir) && !settingsFound;
@@ -270,10 +292,8 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
             }
         }
         if (settingsFound) {
-            returnedExecuter.withSearchUpwards();
+            gradleExecuter.withSearchUpwards();
         }
-
-        return returnedExecuter;
     }
 
     private TestFile getTmpDir() {
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
index 56f606a..74bf7b6 100644
--- 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
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.fixtures;
 
+import groovy.lang.Closure;
+import org.gradle.api.Action;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 
 import java.io.File;
@@ -60,6 +62,11 @@ public interface GradleExecuter {
     GradleExecuter withArguments(List<String> args);
 
     /**
+     * Adds an additional command-line argument to use when executing the build.
+     */
+    GradleExecuter withArgument(String arg);
+
+    /**
      * Sets the environment variables to use when executing the build. Defaults to the environment of this process.
      */
     GradleExecuter withEnvironmentVars(Map<String, ?> environment);
@@ -79,17 +86,23 @@ public interface GradleExecuter {
     GradleExecuter usingBuildScript(File buildScript);
 
     /**
-     * Sets the user home dir. Set to null to use the default user home dir.
+     * Sets the user's home dir to use when running the build. Implementations are not 100% accurate.
      */
     GradleExecuter withUserHomeDir(File userHomeDir);
 
     /**
-     * Sets the java home dir. Set to null to use the default java home dir.
+     * Sets the <em>Gradle</em> user home dir. Setting to null requests that the executer use the real default Gradle user home dir rather than the
+     * default used for testing.
+     */
+    GradleExecuter withGradleUserHomeDir(File userHomeDir);
+
+    /**
+     * Sets the java home dir. Setting to null requests that the executer use the real default java home dir rather than the default used for testing.
      */
     GradleExecuter withJavaHome(File userHomeDir);
 
     /**
-     * Sets the executable to use. Set to null to use the default executable (if any)
+     * Sets the executable to use. Set to null to use the read default executable (if any) rather than the default used for testing.
      */
     GradleExecuter usingExecutable(String script);
 
@@ -104,6 +117,11 @@ public interface GradleExecuter {
     GradleExecuter withStdIn(InputStream stdin);
 
     /**
+     * Specifies that the executer should not set any default jvm args.
+     */
+    GradleExecuter withNoDefaultJvmArgs();
+
+    /**
      * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
      *
      * @return The result.
@@ -132,7 +150,7 @@ public interface GradleExecuter {
     GradleHandle start();
 
     /**
-     * Only makes sense for the forking executor or foreground daemon.
+     * Adds options that should be used to start the JVM, if a JVM is to be started. Ignored if not.
      *
      * @param gradleOpts the jvm opts
      *
@@ -148,4 +166,39 @@ public interface GradleExecuter {
      * @return this executer
      */
     GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding);
+
+    /**
+     * Set the number of seconds an idle daemon should live for.
+     *
+     * @param secs
+     *
+     * @return this executer
+     */
+    GradleExecuter withDaemonIdleTimeoutSecs(int secs);
+
+    /**
+     * Set the working space for the daemon and launched daemons
+     *
+     * @param baseDir
+     *
+     * @return this executer
+     */
+    GradleExecuter withDaemonBaseDir(File baseDir);
+
+    /**
+     * Asserts that this executer will be able to run a build, given its current configuration.
+     *
+     * @throws AssertionError When this executer will not be able to run a build.
+     */
+    void assertCanExecute() throws AssertionError;
+
+    /**
+     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
+     */
+    void beforeExecute(Action<? super GradleExecuter> action);
+
+    /**
+     * Adds an action to be called immediately before execution, to allow extra configuration to be injected.
+     */
+    void beforeExecute(Closure action);
 }
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
index 9fa1d26..688ebde 100644
--- 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
@@ -25,8 +25,4 @@ public interface GradleHandle {
     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
deleted file mode 100755
index 0b2ceb9..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
+++ /dev/null
@@ -1,639 +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.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 java.security.Principal
-import java.util.zip.GZIPOutputStream
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
-
-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 TestUserRealm realm
-    private SecurityHandler securityHandler
-    AuthScheme authenticationScheme = AuthScheme.BASIC
-    private Throwable failure
-    private final List<Expection> expections = []
-
-    enum AuthScheme {
-        BASIC(new BasicAuthHandler()), DIGEST(new DigestAuthHandler())
-
-        final AuthSchemeHandler handler;
-
-        AuthScheme(AuthSchemeHandler handler) {
-            this.handler = handler
-        }
-    }
-
-    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
-                }
-                onFailure(new AssertionError("Received unexpected ${request.method} request to ${target}."))
-                response.sendError(404, "'$target' does not exist")
-            }
-        })
-        server.setHandler(handlers)
-    }
-
-    void start() {
-        server.start()
-    }
-
-    void stop() {
-        resetExpectations()
-        server?.stop()
-    }
-
-    private void onFailure(Throwable failure) {
-        logger.error(failure.message)
-        if (this.failure == null) {
-            this.failure = failure
-        }
-    }
-
-    void resetExpectations() {
-        try {
-            if (failure != null) {
-                throw failure
-            }
-            for (Expection e in expections) {
-                e.assertMet()
-            }
-        } finally {
-            failure = null
-            expections.clear()
-            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))
-    }
-
-    /**
-     * Adds a given file at the given URL. The source file can be either a file or a directory.
-     */
-    void allowHead(String path, File srcFile) {
-        allow(path, true, ['HEAD'], fileHandler(path, srcFile))
-    }
-
-    /**
-     * Adds a given file at the given URL with the given credentials. The source file can be either a file or a directory.
-     */
-    void allowGet(String path, String username, String password, File srcFile) {
-        allow(path, true, ['GET', 'HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
-    }
-
-    private Action fileHandler(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
-        return new Action() {
-            String getDisplayName() {
-                return "return contents of $srcFile.name"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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, "'$request.pathInfo' does not exist")
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds a broken resource at the given URL.
-     */
-    void addBroken(String path) {
-        allow(path, true, null, new Action() {
-            String getDisplayName() {
-                return "return 500 broken"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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'], notFound())
-    }
-
-    /**
-     * Allows one HEAD request for the given URL, which return 404 status code
-     */
-    void expectHeadMissing(String path) {
-        expect(path, false, ['HEAD'], notFound())
-    }
-
-    private Action notFound() {
-        new Action() {
-            String getDisplayName() {
-                return "return 404 not found"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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 HEAD request for the given URL with http authentication.
-     */
-    void expectHead(String path, String username, String password, File srcFile, Long lastModified = null, Long contentLength = null) {
-        expect(path, false, ['HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
-    }
-
-    /**
-     * 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 Action() {
-            String getDisplayName() {
-                return "return gzipped $srcFile.name"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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 Action() {
-            String getDisplayName() {
-                return "redirect to $location"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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 Action() {
-            String getDisplayName() {
-                return "return listing of directory $directory.name"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                sendDirectoryListing(response, directory)
-            }
-        })
-    }
-
-    /**
-     * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
-     */
-    void expectGetDirectoryListing(String path, String username, String password, File directory) {
-        expect(path, false, ['GET'], withAuthentication(path, username, password, new Action() {
-            String getDisplayName() {
-                return "return listing of directory $directory.name"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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/html")
-        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 Action() {
-            String getDisplayName() {
-                return "write request to $destFile.name and return status $statusCode"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                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 Action() {
-            String getDisplayName() {
-                return "write request to $destFile.name"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                if (request.remoteUser != username) {
-                    response.sendError(500, "unexpected username '${request.remoteUser}'")
-                    return
-                }
-                destFile.bytes = request.inputStream.bytes
-            }
-        }))
-    }
-
-    /**
-     * Allows PUT requests with the given credentials.
-     */
-    void allowPut(String path, String username, String password) {
-        allow(path, false, ['PUT'], withAuthentication(path, username, password, new Action() {
-            String getDisplayName() {
-                return "return 500"
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                response.sendError(500, "unexpected username '${request.remoteUser}'")
-            }
-        }))
-    }
-
-    private Action withAuthentication(String path, String username, String password, Action action) {
-        if (realm != null) {
-            assert realm.username == username
-            assert realm.password == password
-            authenticationScheme.handler.addConstraint(securityHandler, path)
-        } else {
-            realm = new TestUserRealm()
-            realm.username = username
-            realm.password = password
-            securityHandler = authenticationScheme.handler.createSecurityHandler(path, realm)
-            collection.addHandler(securityHandler)
-        }
-
-        return new Action() {
-            String getDisplayName() {
-                return action.displayName
-            }
-
-            void handle(HttpServletRequest request, HttpServletResponse response) {
-                if (request.remoteUser != username) {
-                    response.sendError(500, "unexpected username '${request.remoteUser}'")
-                    return
-                }
-                action.handle(request, response)
-            }
-        }
-    }
-
-    private void expect(String path, boolean recursive, Collection<String> methods, Action action) {
-        ExpectOne expectation = new ExpectOne(action, methods, path)
-        expections << expectation
-        add(path, recursive, methods, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                if (expectation.run) {
-                    return
-                }
-                expectation.run = true
-                action.handle(request, response)
-                request.handled = true
-            }
-        })
-    }
-
-    private void allow(String path, boolean recursive, Collection<String> methods, Action action) {
-        add(path, recursive, methods, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                action.handle(request, response)
-                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() {
-        def port = server.connectors[0].localPort
-        if (port < 0) {
-            throw new RuntimeException("""No port available for HTTP server. Still starting perhaps?
-connector: ${server.connectors[0]}
-connector state: ${server.connectors[0].dump()}
-server state: ${server.dump()}
-""")
-        }
-        return port
-    }
-
-    interface Expection {
-        void assertMet()
-    }
-
-    static class ExpectOne implements Expection {
-        boolean run
-        final Action action
-        final Collection<String> methods
-        final String path
-
-        ExpectOne(Action action, Collection<String> methods, String path) {
-            this.action = action
-            this.methods = methods
-            this.path = path
-        }
-
-        void assertMet() {
-            if (!run) {
-                throw new AssertionError("Expected HTTP request not received: ${methods.size() == 1 ? methods[0] : methods} $path and $action.displayName")
-            }
-        }
-    }
-
-    interface Action {
-        String getDisplayName()
-
-        void handle(HttpServletRequest request, HttpServletResponse response)
-    }
-
-    abstract static class AuthSchemeHandler {
-        public SecurityHandler createSecurityHandler(String path, TestUserRealm realm) {
-            def constraintMapping = createConstraintMapping(path)
-            def securityHandler = new SecurityHandler()
-            securityHandler.userRealm = realm
-            securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
-            securityHandler.authenticator = authenticator
-            return securityHandler
-        }
-
-        public void addConstraint(SecurityHandler securityHandler, String path) {
-            securityHandler.constraintMappings = (securityHandler.constraintMappings as List) + createConstraintMapping(path)
-        }
-
-        private ConstraintMapping createConstraintMapping(String path) {
-            def constraint = new Constraint()
-            constraint.name = constraintName()
-            constraint.authenticate = true
-            constraint.roles = ['*'] as String[]
-            def constraintMapping = new ConstraintMapping()
-            constraintMapping.pathSpec = path
-            constraintMapping.constraint = constraint
-            return constraintMapping
-        }
-
-        protected abstract String constraintName();
-
-        protected abstract Authenticator getAuthenticator();
-    }
-
-    public static class BasicAuthHandler extends AuthSchemeHandler {
-        @Override
-        protected String constraintName() {
-            return Constraint.__BASIC_AUTH
-        }
-
-        @Override
-        protected Authenticator getAuthenticator() {
-            return new BasicAuthenticator()
-        }
-    }
-
-    public static class DigestAuthHandler extends AuthSchemeHandler {
-        @Override
-        protected String constraintName() {
-            return Constraint.__DIGEST_AUTH
-        }
-
-        @Override
-        protected Authenticator getAuthenticator() {
-            return new DigestAuthenticator()
-        }
-    }
-
-    static class TestUserRealm implements UserRealm {
-        String username
-        String password
-
-        Principal authenticate(String username, Object credentials, Request request) {
-            Password passwordCred = new Password(password)
-            if (username == this.username && passwordCred.check(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
index 55e405e..89e68af 100644
--- 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
@@ -34,6 +34,7 @@ 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.execution.MultipleBuildFailures;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.internal.Factory;
@@ -51,13 +52,15 @@ import java.io.InputStream;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
 import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 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);
+class InProcessGradleExecuter extends AbstractGradleExecuter {
+    private final ProcessEnvironment processEnvironment = NativeServices.getInstance().get(ProcessEnvironment.class);
 
     @Override
     protected ExecutionResult doRun() {
@@ -105,8 +108,6 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
 
     private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener,
                               BuildListenerImpl listener) {
-        assertCanExecute();
-
         InputStream originalStdIn = System.in;
         System.setIn(getStdin());
         
@@ -129,6 +130,9 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
             previousEnv.put(entry.getKey(), System.getenv(entry.getKey()));
             processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), entry.getValue());
         }
+        if (getUserHomeDir() != null) {
+            System.setProperty("user.home", getUserHomeDir().getPath());
+        }
 
         DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
         factory.addListener(listener);
@@ -163,18 +167,9 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         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>();
+        private final List<String> executedTasks = new CopyOnWriteArrayList<String>();
+        private final Set<String> skippedTasks = new CopyOnWriteArraySet<String>();
 
         public void graphPopulated(TaskExecutionGraph graph) {
             List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
@@ -219,7 +214,6 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         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;
@@ -228,9 +222,7 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         }
 
         public void beforeExecute(Task task) {
-            assertThat(current, nullValue());
             assertTrue(planned.contains(task));
-            current = task;
 
             String taskPath = path(task);
             if (taskPath.startsWith(":buildSrc:")) {
@@ -241,9 +233,6 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         }
 
         public void afterExecute(Task task, TaskState state) {
-            assertThat(task, sameInstance(current));
-            current = null;
-
             String taskPath = path(task);
             if (taskPath.startsWith(":buildSrc:")) {
                 return;
@@ -277,6 +266,11 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
             return output;
         }
 
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+            new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+            return this;
+        }
+
         public String getError() {
             return error;
         }
@@ -351,14 +345,23 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         }
 
         public ExecutionFailure assertThatCause(final Matcher<String> matcher) {
-            if (failure instanceof LocationAwareException) {
-                LocationAwareException exception = (LocationAwareException) failure;
-                assertThat(exception.getReportableCauses(), hasItem(hasMessage(matcher)));
+            List<Throwable> causes = new ArrayList<Throwable>();
+            extractCauses(failure, causes);
+            assertThat(causes, hasItem(hasMessage(matcher)));
+            return this;
+        }
+
+        private void extractCauses(Throwable failure, List<Throwable> causes) {
+            if (failure instanceof MultipleBuildFailures) {
+                MultipleBuildFailures exception = (MultipleBuildFailures) failure;
+                for (Throwable componentFailure : exception.getCauses()) {
+                    extractCauses(componentFailure, causes);
+                }
+            } else if (failure instanceof LocationAwareException) {
+                causes.addAll(((LocationAwareException) failure).getReportableCauses());
             } else {
-                assertThat(failure.getCause(), notNullValue());
-                assertThat(failure.getCause().getMessage(), matcher);
+                causes.add(failure.getCause());
             }
-            return this;
         }
 
         public ExecutionFailure assertHasNoCause() {
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
deleted file mode 100644
index 863b277..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
+++ /dev/null
@@ -1,293 +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 java.util.regex.Pattern
-
-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
-    }
-
-    TestFile getIvyFile() {
-        return moduleDir.file("ivy-${revision}.xml")
-    }
-
-    TestFile getJarFile() {
-        return moduleDir.file("$module-${revision}.jar")
-    }
-
-    TestFile sha1File(File file) {
-        return moduleDir.file("${file.name}.sha1")
-    }
-
-    /**
-     * 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()
-
-        publish(ivyFile) {
-            ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
-<ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
-    <!-- ${publishCount} -->
-	<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 ->
-                def artifactFile = file(artifact)
-                publish(artifactFile) {
-                    artifactFile << "${artifactFile.name} : $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 TestFile file(artifact) {
-        return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
-    }
-
-    private publish(File file, Closure cl) {
-        def lastModifiedTime = file.exists() ? file.lastModified() : null
-        cl.call(file)
-        if (lastModifiedTime != null) {
-            file.setLastModified(lastModifiedTime + 2000)
-        }
-        sha1File(file).text = getHash(file, "SHA1")
-    }
-
-    /**
-     * Asserts that exactly the given artifacts have been published.
-     */
-    void assertArtifactsPublished(String... names) {
-        Set allFileNames = [];
-        for (name in names) {
-            allFileNames += [name, "${name}.sha1"]
-        }
-        assert moduleDir.list() as Set == allFileNames
-    }
-
-    void assertChecksumPublishedFor(TestFile testFile) {
-        def sha1File = sha1File(testFile)
-        sha1File.assertIsFile()
-        assert sha1File.text == getHash(testFile, "SHA1")
-    }
-
-    String getHash(File file, String algorithm) {
-        return HashUtil.createHash(file, algorithm).asHexString()
-    }
-
-    IvyDescriptor getIvy() {
-        return new IvyDescriptor(ivyFile)
-    }
-
-    def expectIvyHead(HttpServer server, prefix = null) {
-        server.expectHead(ivyPath(prefix), ivyFile)
-    }
-
-    def expectIvyGet(HttpServer server, prefix = null) {
-        server.expectGet(ivyPath(prefix), ivyFile)
-    }
-
-    def ivyPath(prefix = null) {
-        path(prefix, ivyFile.name)
-    }
-
-    def expectIvySha1Get(HttpServer server, prefix = null) {
-        server.expectGet(ivySha1Path(prefix), sha1File(ivyFile))
-    }
-
-    def ivySha1Path(prefix = null) {
-        ivyPath(prefix) + ".sha1"
-    }
-
-    def expectArtifactHead(HttpServer server, prefix = null) {
-        server.expectHead(artifactPath(prefix), jarFile)
-    }
-
-    def expectArtifactGet(HttpServer server, prefix = null) {
-        server.expectGet(artifactPath(prefix), jarFile)
-    }
-
-    def artifactPath(prefix = null) {
-        path(prefix, jarFile.name)
-    }
-
-    def expectArtifactSha1Get(HttpServer server, prefix = null) {
-        server.expectGet(artifactSha1Path(prefix), sha1File(jarFile))
-    }
-
-    def artifactSha1Path(prefix = null) {
-        artifactPath(prefix) + ".sha1"
-    }
-
-    def path(prefix = null, String filename) {
-        "${prefix == null ? "" : prefix}/${organisation}/${module}/${revision}/${filename}"
-    }
-}
-
-class IvyDescriptor {
-    final Map<String, IvyConfiguration> configurations = [:]
-    Map<String, IvyArtifact> artifacts = [:]
-
-    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)
-        }
-        ivy.publications.artifact.each { artifact ->
-            def ivyArtifact = new IvyArtifact(name: artifact. at name, type: artifact. at type, ext: artifact. at ext, conf: artifact. at conf.split(",") as List)
-            artifacts.put(ivyArtifact.name, ivyArtifact)
-        }
-    }
-
-    IvyArtifact expectArtifact(String name) {
-        assert artifacts.containsKey(name)
-        artifacts[name]
-    }
-}
-
-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 AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
-        }
-    }
-}
-
-class IvyArtifact {
-    String name
-    String type
-    String ext
-    List<String> conf
-}
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
index 32c7162..af376bd 100644
--- 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
@@ -18,6 +18,7 @@ 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
 
@@ -28,6 +29,10 @@ class JUnitTestExecutionResult implements TestExecutionResult {
         this.buildDir = projectDir.file(buildDirName)
     }
 
+    boolean hasJUnitXmlResults() {
+        xmlResultsDir().list().length > 0
+    }
+
     TestExecutionResult assertTestClassesExecuted(String... testClasses) {
         Map<String, File> classes = findClasses()
         assertThat(classes.keySet(), equalTo(testClasses as Set));
@@ -47,7 +52,7 @@ class JUnitTestExecutionResult implements TestExecutionResult {
     }
 
     private def findClasses() {
-        buildDir.file('test-results').assertIsDir()
+        xmlResultsDir().assertIsDir()
         buildDir.file('reports/tests/index.html').assertIsFile()
 
         Map<String, File> classes = [:]
@@ -59,6 +64,10 @@ class JUnitTestExecutionResult implements TestExecutionResult {
         }
         return classes
     }
+
+    private TestFile xmlResultsDir() {
+        buildDir.file('test-results')
+    }
 }
 
 class JUnitTestClassExecutionResult implements TestClassExecutionResult {
@@ -77,6 +86,13 @@ class JUnitTestClassExecutionResult implements TestClassExecutionResult {
         this
     }
 
+    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+        assert testClassNode. at tests == tests
+        assert testClassNode. at failures == failures
+        assert testClassNode. at errors == errors
+        this
+    }
+
     TestClassExecutionResult assertTestPassed(String name) {
         Map<String, Node> testMethods = findTests()
         assertThat(testMethods.keySet(), hasItem(name))
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/KillProcessAvailability.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/KillProcessAvailability.groovy
new file mode 100644
index 0000000..1a2fb0f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/KillProcessAvailability.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.fixtures
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.process.internal.ExecHandleBuilder
+
+/**
+ * by Szczepan Faber, created at: 9/24/12
+ */
+class KillProcessAvailability {
+
+    final static CAN_KILL
+
+    static {
+        if (OperatingSystem.current().isUnix()) {
+            CAN_KILL = true
+        } else if (OperatingSystem.current().isWindows()) {
+            //On some windowses, taskkill does not seem to work when triggered from java
+            //On our CIs this works fine
+            def e = new ExecHandleBuilder()
+                    .commandLine("taskkill.exe", "/?")
+                    .redirectErrorStream()
+                    .workingDir(new File(".").absoluteFile) //does not matter
+                    .build()
+            e.start()
+            def result = e.waitForFinish()
+            CAN_KILL = result.exitValue == 0
+        } else {
+            CAN_KILL = false
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy
new file mode 100644
index 0000000..3ba85ac
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileModule.groovy
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.xml.MarkupBuilder
+import org.gradle.util.TestFile
+import org.gradle.util.hash.HashUtil
+
+import java.text.SimpleDateFormat
+
+class MavenFileModule implements MavenModule {
+    final TestFile moduleDir
+    final String groupId
+    final String artifactId
+    final String version
+    String parentPomSection
+    String type = 'jar'
+    String packaging
+    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;
+
+    MavenFileModule(TestFile moduleDir, String groupId, String artifactId, String version) {
+        this.moduleDir = moduleDir
+        this.groupId = groupId
+        this.artifactId = artifactId
+        this.version = version
+    }
+
+    MavenFileModule dependsOn(String ... dependencyArtifactIds) {
+        for (String id : dependencyArtifactIds) {
+            dependsOn(groupId, id, '1.0')
+        }
+        return this
+    }
+
+    MavenFileModule dependsOn(String group, String artifactId, String version, String type = null) {
+        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
+        return this
+    }
+
+    /**
+     * Specifies the type of the main artifact.
+     */
+    MavenFileModule hasType(String type) {
+        this.type = type
+        return this
+    }
+
+    /**
+     * Adds an additional artifact to this module.
+     * @param options Can specify any of: type or classifier
+     */
+    MavenFileModule artifact(Map<String, ?> options) {
+        artifacts << options
+        return this
+    }
+
+    MavenFileModule withNonUniqueSnapshots() {
+        uniqueSnapshots = false;
+        return this;
+    }
+
+    /**
+     * 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")
+        }
+        assert moduleDir.isDirectory()
+        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 getRootMetaDataFile() {
+        moduleDir.parentFile.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)
+    }
+
+    MavenFileModule 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)
+    }
+
+    MavenModule publish() {
+        moduleDir.createDir()
+        def rootMavenMetaData = getRootMetaDataFile()
+
+        updateRootMavenMetaData(rootMavenMetaData)
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            def metaDataFile = moduleDir.file('maven-metadata.xml')
+            publish(metaDataFile) {
+                metaDataFile.text = """
+<metadata>
+  <!-- $publishCount -->
+  <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>
+"""
+            }
+        }
+
+        publish(pomFile) {
+            def pomPackaging = packaging ?: type;
+            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>$pomPackaging</packaging>
+  <version>$version</version>
+  <description>Published on $publishTimestamp</description>"""
+
+            if (parentPomSection) {
+                pomFile << "\n$parentPomSection\n"
+            }
+
+            if (!dependencies.empty) {
+                pomFile << """
+  <dependencies>"""
+            }
+
+            dependencies.each { dependency ->
+                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
+                pomFile << """
+    <dependency>
+      <groupId>$dependency.groupId</groupId>
+      <artifactId>$dependency.artifactId</artifactId>
+      <version>$dependency.version</version>
+      $typeAttribute
+    </dependency>"""
+            }
+
+            if (!dependencies.empty) {
+                pomFile << """
+  </dependencies>"""
+            }
+
+            pomFile << "\n</project>"
+        }
+
+        artifacts.each { artifact ->
+            publishArtifact(artifact)
+        }
+        publishArtifact([:])
+        return this
+    }
+
+    private void updateRootMavenMetaData(TestFile rootMavenMetaData) {
+        def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
+        allVersions << version;
+        publish(rootMavenMetaData) {
+            rootMavenMetaData.withWriter {writer ->
+                def builder = new MarkupBuilder(writer)
+                builder.metadata {
+                    groupId(groupId)
+                    artifactId(artifactId)
+                    version(allVersions.max())
+                    versioning {
+                        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+                            snapshot {
+                                timestamp(timestampFormat.format(publishTimestamp))
+                                buildNumber(publishCount)
+                                lastUpdated(updateFormat.format(publishTimestamp))
+                            }
+                        } else {
+                            versions {
+                                allVersions.each{currVersion ->
+                                    version(currVersion)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private File publishArtifact(Map<String, ?> artifact) {
+        def artifactFile = artifactFile(artifact)
+        publish(artifactFile) {
+            if (type != 'pom') {
+                artifactFile.text = "${artifactFile.name} : $publishCount"
+            }
+        }
+        return artifactFile
+    }
+
+    private publish(File file, Closure cl) {
+        def lastModifiedTime = file.exists() ? file.lastModified() : null
+        cl.call(file)
+        if (lastModifiedTime != null) {
+            file.setLastModified(lastModifiedTime + 2000)
+        }
+        createHashFiles(file)
+    }
+
+    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(TestFile file, String algorithm) {
+        def hashFile = file.parentFile.file("${file.name}.${algorithm}")
+        hashFile.text = HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
+        return hashFile
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.groovy
new file mode 100644
index 0000000..ba220be
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenFileRepository.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.integtests.fixtures
+
+import org.gradle.util.TestFile
+
+/**
+ * A fixture for dealing with local Maven repositories.
+ */
+class MavenFileRepository implements MavenRepository {
+    final TestFile rootDir
+
+    MavenFileRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    MavenFileModule module(String groupId, String artifactId, Object version = '1.0') {
+        def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version")
+        return new MavenFileModule(artifactDir, groupId, artifactId, version as String)
+    }
+}
+
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.groovy
new file mode 100644
index 0000000..47b1def
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpModule.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.integtests.fixtures
+
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.TestFile
+
+class MavenHttpModule implements MavenModule {
+    private final HttpServer server
+    private final String modulePath
+    private final MavenFileModule backingModule
+
+    MavenHttpModule(HttpServer server, String modulePath, MavenFileModule backingModule) {
+        this.backingModule = backingModule
+        this.server = server
+        this.modulePath = modulePath
+    }
+
+    MavenHttpModule publish() {
+        backingModule.publish()
+        return this
+    }
+
+    MavenHttpModule publishWithChangedContent() {
+        backingModule.publishWithChangedContent()
+        return this
+    }
+
+    MavenHttpModule withNonUniqueSnapshots() {
+        backingModule.withNonUniqueSnapshots()
+        return this
+    }
+
+    MavenModule dependsOn(String group, String artifactId, String version) {
+        backingModule.dependsOn(group, artifactId, version)
+        return this
+    }
+
+    TestFile getPomFile() {
+        return backingModule.pomFile
+    }
+
+    TestFile getArtifactFile() {
+        return backingModule.artifactFile
+    }
+
+    TestFile getMetaDataFile() {
+        return backingModule.metaDataFile
+    }
+
+    void allowAll() {
+        server.allowGetOrHead(modulePath, backingModule.moduleDir)
+    }
+
+    void expectMetaDataGet() {
+        server.expectGet("$modulePath/$metaDataFile.name", metaDataFile)
+    }
+
+    void expectMetaDataGetMissing() {
+        server.expectGetMissing("$modulePath/$metaDataFile.name")
+    }
+
+    void expectPomGet() {
+        server.expectGet("$modulePath/$pomFile.name", pomFile)
+    }
+
+    void expectPomHead() {
+        server.expectHead("$modulePath/$pomFile.name", pomFile)
+    }
+
+    void allowPomHead() {
+        server.allowHead("$modulePath/$pomFile.name", pomFile)
+    }
+
+    void expectPomGetMissing() {
+        server.expectGetMissing("$modulePath/$missingPomName")
+    }
+
+    void expectPomSha1Get() {
+        server.expectGet("$modulePath/${pomFile.name}.sha1", backingModule.sha1File(pomFile))
+    }
+
+    void allowPomSha1Get() {
+        server.allowGetOrHead("$modulePath/${pomFile.name}.sha1", backingModule.sha1File(pomFile))
+    }
+
+    void expectPomSha1GetMissing() {
+        server.expectGetMissing("$modulePath/${missingPomName}.sha1")
+    }
+
+    private String getMissingPomName() {
+        if (backingModule.version.endsWith("-SNAPSHOT")) {
+            return "${backingModule.artifactId}-${backingModule.version}.pom"
+        } else {
+            return pomFile.name
+        }
+    }
+
+    void expectArtifactGet() {
+        server.expectGet("$modulePath/$artifactFile.name", artifactFile)
+    }
+
+    void expectArtifactHead() {
+        server.expectHead("$modulePath/$artifactFile.name", artifactFile)
+    }
+
+    void allowArtifactHead() {
+        server.allowHead("$modulePath/$artifactFile.name", artifactFile)
+    }
+
+    void expectArtifactGetMissing() {
+        server.expectGetMissing("$modulePath/$missingArtifactName")
+    }
+
+    void expectArtifactHeadMissing() {
+        server.expectHeadMissing("$modulePath/$missingArtifactName")
+    }
+
+    void expectArtifactSha1Get() {
+        server.expectGet("$modulePath/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
+    }
+
+    void allowArtifactSha1Get() {
+        server.allowGetOrHead("$modulePath/${artifactFile.name}.sha1", backingModule.sha1File(artifactFile))
+    }
+
+    void expectArtifactSha1GetMissing() {
+        server.expectGetMissing("$modulePath/${missingArtifactName}.sha1")
+    }
+
+    private String getMissingArtifactName() {
+        if (backingModule.version.endsWith("-SNAPSHOT")) {
+            return "${backingModule.artifactId}-${backingModule.version}.jar"
+        } else {
+            return artifactFile.name
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.groovy
new file mode 100644
index 0000000..367223e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenHttpRepository.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.fixtures
+
+import org.gradle.test.fixtures.server.http.HttpServer
+
+/**
+ * A fixture for dealing with remote HTTP Maven repositories.
+ */
+class MavenHttpRepository {
+    private final HttpServer server
+    private final MavenFileRepository backingRepository
+    private final String contextPath
+
+    MavenHttpRepository(HttpServer server, String contextPath = "/repo", MavenFileRepository backingRepository) {
+        if (!contextPath.startsWith("/")) {
+            throw new IllegalArgumentException("Context path must start with '/'")
+        }
+        this.contextPath = contextPath
+        this.server = server
+        this.backingRepository = backingRepository
+    }
+
+    URI getUri() {
+        return new URI("http://localhost:${server.port}${contextPath}")
+    }
+
+    void expectMetaDataGet(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
+        server.expectGet("$contextPath/$path", backingRepository.getRootDir().file(path))
+    }
+
+    void allowMetaDataGet(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
+        server.allowGetOrHead("$contextPath/$path", backingRepository.getRootDir().file(path))
+    }
+
+    void expectMetaDataGetMissing(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/maven-metadata.xml"
+        server.expectGetMissing("$contextPath/$path")
+    }
+
+    void expectDirectoryListGet(String groupId, String artifactId) {
+        def path = "${groupId.replace('.', '/')}/$artifactId/"
+        server.expectGetDirectoryListing("$contextPath/$path", backingRepository.getRootDir().file(path))
+    }
+
+    MavenHttpModule module(String groupId, String artifactId) {
+        return module(groupId, artifactId, "1.0")
+    }
+
+    MavenHttpModule module(String groupId, String artifactId, Object version) {
+        def backingModule = backingRepository.module(groupId, artifactId, version)
+        return new MavenHttpModule(server, "$contextPath/${groupId.replace('.', '/')}/$artifactId/$version", backingModule)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.groovy
new file mode 100644
index 0000000..f28f9c0
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenModule.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.integtests.fixtures
+
+import org.gradle.util.TestFile
+
+interface MavenModule {
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
+     */
+    MavenModule publish()
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with changed content to any
+     * previous publication.
+     */
+    MavenModule publishWithChangedContent()
+
+    MavenModule withNonUniqueSnapshots()
+
+    MavenModule dependsOn(String group, String artifactId, String version)
+
+    TestFile getPomFile()
+
+    TestFile getArtifactFile()
+
+    TestFile getMetaDataFile()
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.groovy
new file mode 100644
index 0000000..ec85479
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenPom.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.integtests.fixtures
+
+class MavenPom {
+    String groupId
+    String artifactId
+    String version
+    String packaging
+    final Map<String, MavenScope> scopes = [:]
+
+    MavenPom(File pomFile) {
+        def pom = new XmlParser().parse(pomFile)
+
+        groupId = pom.groupId[0]?.text()
+        artifactId = pom.artifactId[0]?.text()
+        version = pom.version[0]?.text()
+        packaging = pom.packaging[0]?.text()
+
+        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())
+        }
+    }
+
+}
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
index 1573b7f..095449e 100644
--- 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
@@ -15,413 +15,13 @@
  */
 package org.gradle.integtests.fixtures
 
-import java.text.SimpleDateFormat
-
-import org.gradle.util.TestFile
-import org.gradle.util.hash.HashUtil
-import groovy.xml.MarkupBuilder
-
 /**
  * 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'
-    String packaging
-    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, String type = null) {
-        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
-        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;
-    }
-
-    /**
-     * 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 getRootMetaDataFile() {
-        moduleDir.parentFile.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()
-        def rootMavenMetaData = getRootMetaDataFile()
-
-        updateRootMavenMetaData(rootMavenMetaData)
-        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-            def metaDataFile = moduleDir.file('maven-metadata.xml')
-            publish(metaDataFile) {
-                metaDataFile.text = """
-<metadata>
-  <!-- $publishCount -->
-  <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>
-"""
-            }
-        }
-
-        publish(pomFile) {
-            def pomPackaging = packaging ?: type;
-            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>$pomPackaging</packaging>
-  <version>$version</version>
-  <description>Published on $publishTimestamp</description>"""
-
-            if (parentPomSection) {
-                pomFile << "\n$parentPomSection\n"
-            }
-
-            dependencies.each { dependency ->
-                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
-                pomFile << """
-  <dependencies>
-    <dependency>
-      <groupId>$dependency.groupId</groupId>
-      <artifactId>$dependency.artifactId</artifactId>
-      <version>$dependency.version</version>
-      $typeAttribute
-    </dependency>
-  </dependencies>"""
-            }
-
-            pomFile << "\n</project>"
-        }
-
-        artifacts.each { artifact ->
-            publishArtifact(artifact)
-        }
-        publishArtifact([:])
-        return this
-    }
-
-    private void updateRootMavenMetaData(TestFile rootMavenMetaData) {
-        def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
-        allVersions << version;
-        publish(rootMavenMetaData) {
-            rootMavenMetaData.withWriter {writer ->
-                def builder = new MarkupBuilder(writer)
-                builder.metadata {
-                    groupId(groupId)
-                    artifactId(artifactId)
-                    version(allVersions.max())
-                    versioning {
-                        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-                            snapshot {
-                                timestamp(timestampFormat.format(publishTimestamp))
-                                buildNumber(publishCount)
-                                lastUpdated(updateFormat.format(publishTimestamp))
-                            }
-                        } else {
-                            versions {
-                                allVersions.each{currVersion ->
-                                    version(currVersion)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private File publishArtifact(Map<String, ?> artifact) {
-        def artifactFile = artifactFile(artifact)
-        publish(artifactFile) {
-            if (type != 'pom') {
-                artifactFile << "add some content so that file size isn't zero: $publishCount"
-            }
-        }
-        return artifactFile
-    }
+interface MavenRepository {
+    URI getUri()
 
-    private publish(File file, Closure cl) {
-        def lastModifiedTime = file.exists() ? file.lastModified() : null
-        cl.call(file)
-        if (lastModifiedTime != null) {
-            file.setLastModified(lastModifiedTime + 2000)
-        }
-        createHashFiles(file)
-    }
-
-    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(TestFile file, String algorithm) {
-        def hashFile = file.parentFile.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 allowPomHead(HttpServer server, prefix = null) {
-        server.allowHead(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 allowPomSha1Get(HttpServer server, prefix = null) {
-        server.allowGet(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 allowArtifactHead(HttpServer httpServer, prefix = null) {
-        httpServer.allowHead(artifactPath(prefix), artifactFile)
-    }
-
-    public artifactPath(prefix = null) {
-        path(prefix, artifactFile.name)
-    }
-
-    public expectArtifactSha1Get(HttpServer server, prefix = null) {
-        server.expectGet(artifactSha1Path(prefix), sha1File(artifactFile))
-    }
-
-    public allowArtifactSha1Get(HttpServer server, prefix = null) {
-        server.allowGet(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 {
-    String groupId
-    String artifactId
-    String version
-    String packaging
-    final Map<String, MavenScope> scopes = [:]
-
-    MavenPom(File pomFile) {
-        def pom = new XmlParser().parse(pomFile)
-
-        groupId = pom.groupId[0]?.text()
-        artifactId = pom.artifactId[0]?.text()
-        version = pom.version[0]?.text()
-        packaging = pom.packaging[0]?.text()
-
-        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())
-        }
-    }
+    MavenModule module(String groupId, String artifactId)
 
+    MavenModule module(String groupId, String artifactId, Object version)
 }
-
-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 AssertionError("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/MavenScope.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenScope.groovy
new file mode 100644
index 0000000..e84ac2e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenScope.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.fixtures
+
+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 AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
+        }
+    }
+}
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
index bc175e4..f480977 100644
--- 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
@@ -17,6 +17,7 @@
 package org.gradle.integtests.fixtures
 
 import org.junit.runner.RunWith
+import org.gradle.util.VersionNumber
 
 @RunWith(MultiVersionSpecRunner)
 abstract class MultiVersionIntegrationSpec extends AbstractIntegrationSpec {
@@ -25,4 +26,8 @@ abstract class MultiVersionIntegrationSpec extends AbstractIntegrationSpec {
     String getVersion() {
         version
     }
+
+    static VersionNumber getVersionNumber() {
+        VersionNumber.parse(version)
+    }
 }
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
index 2ea90f2..71eed77 100644
--- 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
@@ -47,6 +47,11 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
         return output;
     }
 
+    public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+        new SequentialOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+        return this;
+    }
+
     public String getError() {
         return error;
     }
@@ -54,7 +59,7 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
     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));
@@ -64,7 +69,7 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
     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));
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
index c609124..6e60d2b 100644
--- 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
@@ -18,19 +18,10 @@ 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));
+        return new OutputScrapingExecutionResult(output, error);
     }
 
     protected ExecutionResult toExecutionFailure(String output, String error) {
-        return new OutputScrapingExecutionFailure(transformStandardOutput(output), transformErrorOutput(error));
+        return new OutputScrapingExecutionFailure(output, 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/ParallelForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleExecuter.java
new file mode 100644
index 0000000..06d213d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleExecuter.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.integtests.fixtures;
+
+import org.gradle.internal.Factory;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.util.TestFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class ParallelForkingGradleExecuter extends ForkingGradleExecuter {
+    public ParallelForkingGradleExecuter(TestFile gradleHomeDir) {
+        super(gradleHomeDir);
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(super.getAllArgs());
+        args.add("--parallel-threads=4");
+        return args;
+    }
+
+    @Override
+    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ParallelForkingGradleHandle(encoding, execHandleFactory);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.java
new file mode 100644
index 0000000..7085d38
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelForkingGradleHandle.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.integtests.fixtures;
+
+import org.gradle.internal.Factory;
+import org.gradle.process.internal.AbstractExecHandleBuilder;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class ParallelForkingGradleHandle extends ForkingGradleHandle {
+
+    public ParallelForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        super(outputEncoding, execHandleFactory);
+    }
+
+    @Override
+    protected ExecutionResult toExecutionResult(String output, String error) {
+        return new ParallelExecutionResult(output, error);
+    }
+
+    @Override
+    protected ExecutionResult toExecutionFailure(String output, String error) {
+        return new ParallelExecutionResult(output, error);
+    }
+
+    /**
+     * Need a different output comparator for parallel execution.
+     */
+    private static class ParallelExecutionResult extends OutputScrapingExecutionFailure {
+        public ParallelExecutionResult(String output, String error) {
+            super(output, error);
+        }
+
+        @Override
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+            assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), new HashSet<String>(getExecutedTasks()), equalTo(expectedTasks));
+            return this;
+        }
+
+        @Override
+        public String getOutput() {
+            String output = super.getOutput();
+            String parallelWarningPrefix = "Parallel project execution is an \"incubating\" feature";
+            if (output.startsWith(parallelWarningPrefix)) {
+                return output.replaceFirst(String.format("(?m)%s.+$\n", parallelWarningPrefix), "");
+            }
+            return output;
+        }
+
+        @Override
+        public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines) {
+            new ParallelOutputMatcher().assertOutputMatches(expectedOutput, getOutput(), ignoreExtraLines);
+            return this;
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.groovy
new file mode 100644
index 0000000..d692e5b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ParallelOutputMatcher.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.integtests.fixtures
+
+import org.junit.Assert
+import org.gradle.internal.SystemProperties
+
+/**
+ * Checks that all lines contained in the expected output are present in the actual output, in any order.
+ */
+class ParallelOutputMatcher extends SequentialOutputMatcher {
+    private static final String NL = SystemProperties.lineSeparator
+
+    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
+        List<String> unmatchedLines = new ArrayList<String>(actualLines)
+        expectedLines.removeAll('')
+        unmatchedLines.removeAll('')
+
+        expectedLines.each { expectedLine ->
+            def matchedLine = unmatchedLines.find { actualLine ->
+                compare(expectedLine, actualLine)
+            }
+            if (matchedLine) {
+                unmatchedLines.remove(matchedLine)
+            } else {
+                Assert.fail("Line missing from output.${NL}${expectedLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+            }
+        }
+
+        if (!(ignoreExtraLines || unmatchedLines.empty)) {
+            def unmatched = unmatchedLines.join(NL)
+            Assert.fail("Extra lines in output.${NL}${unmatched}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+        }
+    }
+}
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
index bff3729..7a57974 100644
--- 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
@@ -27,19 +27,20 @@ import org.gradle.internal.jvm.Jvm
 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.util.TestFile
 
-public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implements BasicGradleDistribution {
+class PreviousGradleVersionExecuter extends AbstractDelegatingGradleExecuter implements BasicGradleDistribution {
     private static final CACHE_FACTORY = createCacheFactory()
 
     private static CacheFactory createCacheFactory() {
         return new DefaultCacheFactory(
                 new DefaultFileLockManager(
                         new DefaultProcessMetaDataProvider(
-                                new NativeServices().get(ProcessEnvironment)))).create()
+                                NativeServices.getInstance().get(ProcessEnvironment)),
+                        20 * 60 * 1000 // allow up to 20 minutes to download a distribution
+                )).create()
     }
 
     private final GradleDistribution dist
@@ -86,10 +87,6 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         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')) {
@@ -117,6 +114,10 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         return version >= GradleVersion.version('1.0-milestone-3')
     }
 
+    boolean isMultiProcessSafeCache() {
+        return version >= GradleVersion.version('1.0-milestone-5')
+    }
+
     boolean wrapperCanExecute(String version) {
         if (version == '0.8' || this.version == GradleVersion.version('0.8')) {
             // There was a breaking change after 0.8
@@ -135,11 +136,12 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         return true
     }
 
-    protected ExecutionResult doRun() {
+    @Override
+    protected GradleExecuter configureExecuter() {
         ForkingGradleExecuter executer = new ForkingGradleExecuter(gradleHomeDir)
         executer.inDirectory(dist.testDir)
         copyTo(executer)
-        return executer.run()
+        return executer
     }
 
     GradleExecuter executer() {
@@ -173,8 +175,4 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         zipFile.assertIsFile()
         homeDir.assertIsDir()
     }
-
-    protected ExecutionFailure doRunWithFailure() {
-        throw new UnsupportedOperationException();
-    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy
new file mode 100644
index 0000000..c267d5c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ProgressLoggingFixture.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.rules.MethodRule
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+class ProgressLoggingFixture implements MethodRule {
+
+    private TestFile loggingOutputFile = null
+
+    boolean downloadProgressLogged(String url) {
+        return progressLogged("Download", url)
+    }
+
+    boolean uploadProgressLogged(String url) {
+        return progressLogged("Upload", url)
+    }
+
+    private boolean progressLogged(String operation, String url) {
+        def lines = loggingOutputFile.exists() ? loggingOutputFile.text.readLines() : []
+        def startIndex = lines.indexOf("[START " + operation + " " + url + "]")
+        if (startIndex == -1) {
+            return false
+        }
+        lines = lines[startIndex..<lines.size()]
+        lines = lines[0..lines.indexOf("[END " + operation + " " + url + "]")]
+        lines.size() >= 2
+    }
+
+    Statement apply(Statement base, FrameworkMethod method, Object target) {
+        TestFile initFile
+        GradleDistributionExecuter executer = RuleHelper.getField(target, GradleDistributionExecuter)
+        GradleDistribution distribution = RuleHelper.getField(target, GradleDistribution)
+        TestFile temporaryFolder = distribution.temporaryFolder.dir
+        loggingOutputFile = temporaryFolder.file("loggingoutput.log")
+        initFile = temporaryFolder.file("progress-logging-init.gradle")
+        initFile.text = """import org.gradle.logging.internal.*
+                           File outputFile = file("${loggingOutputFile.toURI()}")
+                           OutputEventListener outputEventListener = new OutputEventListener() {
+                                void onOutput(OutputEvent event) {
+                                    if (event instanceof ProgressStartEvent) {
+                                        outputFile << "[START \$event.description]\\n"
+                                    } else if (event instanceof ProgressEvent) {
+                                        outputFile << "[\$event.status]\\n"
+                                    } else if (event instanceof ProgressCompleteEvent) {
+                                        outputFile << "[END \$event.description]\\n"
+                                    }
+                                }
+                           }
+                           def loggingOutputInternal = gradle.services.get(LoggingOutputInternal)
+                           loggingOutputInternal.addOutputEventListener(outputEventListener)
+                           buildFinished{
+                                loggingOutputInternal.removeOutputEventListener(outputEventListener)
+                           }"""
+        executer.beforeExecute {
+            usingInitScript(initFile)
+        }
+
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+            }
+        };
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.groovy
new file mode 100644
index 0000000..3469e35
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RedirectMavenCentral.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.integtests.fixtures
+
+import org.gradle.api.Action
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.TemporaryFolder
+
+class RedirectMavenCentral implements Action<GradleExecuter> {
+    private final TemporaryFolder temporaryFolder
+
+    RedirectMavenCentral(TemporaryFolder temporaryFolder) {
+        this.temporaryFolder = temporaryFolder
+    }
+
+    void execute(GradleExecuter executer) {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+
+        def file = temporaryFolder.createFile("redirect-maven-central-init.gradle")
+        file.text = """
+allprojects {
+    repositories.withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository) {
+        if (url == new URI('http://repo1.maven.org/maven2/')) {
+            url = "http://repo.gradle.org/gradle/repo1"
+        }
+    }
+}
+"""
+        executer.withArgument("-I$file.absolutePath")
+    }
+}
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
index 24913a5..ecc3741 100644
--- 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
@@ -19,7 +19,6 @@ 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;
 
@@ -57,17 +56,4 @@ public class ReleasedVersions {
             }
         });
     }
-
-    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/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
deleted file mode 100644
index 23f7b21..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SFTPServer.groovy
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy
new file mode 100644
index 0000000..8a7eb23
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SequentialOutputMatcher.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.SystemProperties
+import org.junit.Assert
+import org.gradle.util.TextUtil
+
+/**
+ * Check that the actual output lines match the expected output lines in content and order.
+ */
+class SequentialOutputMatcher {
+    private static final String NL = SystemProperties.lineSeparator
+
+    public void assertOutputMatches(String expected, String actual, boolean ignoreExtraLines) {
+        List actualLines = normaliseOutput(actual.readLines())
+        List expectedLines = expected.readLines()
+        assertOutputLinesMatch(expectedLines, actualLines, ignoreExtraLines, actual)
+    }
+
+    protected void assertOutputLinesMatch(List<String> expectedLines, List<String> actualLines, boolean ignoreExtraLines, String actual) {
+        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}---")
+        }
+    }
+
+    private 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
+    }
+
+    protected 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 file separators
+        actual = TextUtil.normaliseFileSeparators(actual)
+
+        return actual == expected
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
index d389a97..027b1bd 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
@@ -24,6 +24,8 @@ public interface TestClassExecutionResult {
      */
     TestClassExecutionResult assertTestsExecuted(String... testNames);
 
+    TestClassExecutionResult assertTestCount(int tests, int failures, int errors);
+
     /**
      * Asserts that the given tests (and only the given tests) were skipped for the given test class.
      */
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
index e0c5bb2..49a1065 100644
--- 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
@@ -25,24 +25,51 @@ import org.hamcrest.Matcher
  */
 class TestNGExecutionResult implements TestExecutionResult {
     private final TestFile projectDir
-    private final GPathResult resultsXml
+    private GPathResult resultsXml
+    public static final String DEFAULT_TESTNG_REPORT = "build/reports/tests"
 
     def TestNGExecutionResult(projectDir) {
         this.projectDir = projectDir;
-        resultsXml = new XmlSlurper().parse(projectDir.file('build/reports/tests/testng-results.xml').assertIsFile())
+    }
+
+    boolean hasTestNGXmlResults() {
+        xmlReportFile().isFile()
+    }
+
+    boolean hasJUnitResultsGeneratedByTestNG() {
+        def dir = projectDir.file("$DEFAULT_TESTNG_REPORT/junitreports")
+        dir.isDirectory() && dir.list().length > 0
+    }
+
+    boolean hasHtmlResults() {
+        htmlReportFile().isFile()
     }
 
     TestExecutionResult assertTestClassesExecuted(String... testClasses) {
-        projectDir.file('build/reports/tests/index.html').assertIsFile()
+        parseResults()
+        htmlReportFile().assertIsFile()
         def actualTestClasses = findTestClasses().keySet()
         org.junit.Assert.assertThat(actualTestClasses, org.hamcrest.Matchers.equalTo(testClasses as Set))
         this
     }
 
+    private TestFile htmlReportFile() {
+        projectDir.file('build/reports/tests/index.html')
+    }
+
     TestClassExecutionResult testClass(String testClass) {
+        parseResults()
         return new org.gradle.integtests.fixtures.TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
     }
 
+    private void parseResults() {
+        resultsXml = new XmlSlurper().parse(xmlReportFile().assertIsFile())
+    }
+
+    private TestFile xmlReportFile() {
+        projectDir.file("$DEFAULT_TESTNG_REPORT/testng-results.xml")
+    }
+
     private def findTestClass(String testClass) {
         def testClasses = findTestClasses()
         if (!testClasses.containsKey(testClass)) {
@@ -69,6 +96,10 @@ private class TestNgTestClassExecutionResult implements TestClassExecutionResult
         this.testClassNode = resultXml
     }
 
+    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+        throw new RuntimeException("Unsupported. Implement if you need it.");
+    }
+
     TestClassExecutionResult assertTestsExecuted(String... testNames) {
         def actualTestMethods = findTestMethods().keySet()
         org.junit.Assert.assertThat(actualTestMethods, org.hamcrest.Matchers.equalTo(testNames as Set))
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
deleted file mode 100644
index b1d1de0..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNativeFileSystem.groovy
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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
deleted file mode 100644
index 4ef413b..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestProxyServer.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
-
-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/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
index b6b5cc3..3373825 100644
--- 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
@@ -20,29 +20,33 @@ 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.gradle.util.AntUtil
 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
+import org.gradle.util.TextUtil
 
 class UserGuideSamplesRunner extends Runner {
     private static final String NL = SystemProperties.lineSeparator
 
     Class<?> testClass
     Description description
-    Map<Description, SampleRun> samples;
+    Map<Description, SampleRun> samples
     GradleDistribution dist = new GradleDistribution()
     GradleDistributionExecuter executer = new GradleDistributionExecuter(dist)
-    Pattern dirFilter = null 
+    Pattern dirFilter = null
+    List excludes
 
     def UserGuideSamplesRunner(Class<?> testClass) {
         this.testClass = testClass
         this.description = Description.createSuiteDescription(testClass)
         this.dirFilter = getDirFilterPattern()
+        this.excludes = getExcludes()
         samples = new LinkedHashMap()
         for (sample in getScriptsForSamples(dist.userGuideInfoDir)) {
             if (shouldInclude(sample)) {
@@ -61,11 +65,23 @@ class UserGuideSamplesRunner extends Runner {
         filter ? Pattern.compile(filter) : null
     }
 
+    def getExcludes() {
+        List excludes = []
+        String excludesString = System.properties["org.gradle.userguide.samples.exclude"] ?: "";
+        excludesString.split(',').each {
+            excludes.add it
+        }
+        return excludes
+    }
+
     Description getDescription() {
         return description
     }
 
     private boolean shouldInclude(SampleRun run) {
+        if (excludes.contains(run.id)) {
+            return false
+        }
         dirFilter ? run.subDir ==~ dirFilter : true
     }
 
@@ -106,8 +122,9 @@ class UserGuideSamplesRunner extends Runner {
             ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
             if (run.outputFile) {
                 String expectedResult = replaceWithPlatformNewLines(dist.userGuideOutputDir.file(run.outputFile).text)
+                expectedResult = replaceWithRealSamplesDir(expectedResult)
                 try {
-                    compareStrings(expectedResult, result.output, run.ignoreExtraLines)
+                    result.assertOutputEquals(expectedResult, run.ignoreExtraLines)
                 } catch (AssertionFailedError e) {
                     println 'Expected Result:'
                     println expectedResult
@@ -135,70 +152,15 @@ class UserGuideSamplesRunner extends Runner {
         }
     }
 
-    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) {
+    private 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
+    private String replaceWithRealSamplesDir(String text) {
+        def normalisedSamplesDir = TextUtil.normaliseFileSeparators(dist.samplesDir.absolutePath)
+        return text.replaceAll(Pattern.quote('/home/user/gradle/samples'), normalisedSamplesDir)
     }
 
     static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
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
index 450e5e0..7f16a68 100644
--- 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
@@ -21,7 +21,7 @@ import java.util.regex.Pattern
 abstract class WellBehavedPluginTest extends AbstractIntegrationSpec {
 
     String getPluginId() {
-        def matcher = Pattern.compile("(\\w+)Plugin(GoodBehaviour)?(Integration)?Test").matcher(getClass().simpleName)
+        def matcher = Pattern.compile("(\\w+)Plugin(GoodBehaviour)?(Integ(ration)?)?Test").matcher(getClass().simpleName)
         if (matcher.matches()) {
             return matcher.group(1).toLowerCase()
         }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/AbstractIvyModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/AbstractIvyModule.groovy
new file mode 100644
index 0000000..63528d6
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/AbstractIvyModule.groovy
@@ -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.test.fixtures.ivy
+
+abstract class AbstractIvyModule implements IvyModule {
+
+    IvyDescriptor getIvy() {
+        return new IvyDescriptor(ivyFile)
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
new file mode 100644
index 0000000..34c9092
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.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.test.fixtures.ivy
+
+import groovy.xml.QName
+
+import java.util.regex.Pattern
+
+class IvyDescriptor {
+    final Map<String, IvyDescriptorDependencyConfiguration> dependencies = [:]
+    Map<String, IvyDescriptorArtifact> artifacts = [:]
+    Map<String, IvyDescriptorConfiguration> configurations = [:]
+    String rev
+
+    IvyDescriptor(File ivyFile) {
+        def ivy = new XmlParser().parse(ivyFile)
+        rev = ivy. at rev
+
+        ivy.configurations[0].conf.each {
+            configurations[it. at name] = new IvyDescriptorConfiguration(
+                    name: it. at name, visibility: it. at visibility, description: it. at description,
+                    extend: (it. at extends ?: "").split(",")*.trim()
+            )
+        }
+
+        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 = dependencies[configName]
+            if (!config) {
+                config = new IvyDescriptorDependencyConfiguration()
+                dependencies[configName] = config
+            }
+            config.addDependency(dep. at org, dep. at name, dep. at rev)
+        }
+
+        ivy.publications.artifact.each { artifact ->
+            def ivyArtifact = new IvyDescriptorArtifact(
+                    name: artifact. at name, type: artifact. at type,
+                    ext: artifact. at ext, conf: artifact. at conf.split(",") as List,
+                    mavenAttributes: artifact.attributes().findAll { it.key instanceof QName && it.key.namespaceURI == "http://ant.apache.org/ivy/maven" }.collectEntries { [it.key.localPart, it.value] }
+            )
+
+            artifacts.put(ivyArtifact.name, ivyArtifact)
+        }
+
+    }
+
+    IvyDescriptorArtifact expectArtifact(String name) {
+        assert artifacts.containsKey(name)
+        artifacts[name]
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy
new file mode 100644
index 0000000..4fdf73e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy
@@ -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.test.fixtures.ivy
+
+class IvyDescriptorArtifact {
+    String name
+    String type
+    String ext
+    List<String> conf
+    Map<String, String> mavenAttributes
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorConfiguration.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorConfiguration.groovy
new file mode 100644
index 0000000..498e608
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorConfiguration.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.test.fixtures.ivy
+
+class IvyDescriptorConfiguration {
+
+    String name
+    String visibility
+    String description
+    Set<String> extend
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.groovy
new file mode 100644
index 0000000..66a8652
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.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.test.fixtures.ivy
+
+class IvyDescriptorDependencyConfiguration {
+    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 AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
new file mode 100644
index 0000000..91e007f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.ivy
+
+import org.apache.ivy.core.IvyPatternHelper
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.util.TestFile
+import org.gradle.util.hash.HashUtil
+
+class IvyFileModule extends AbstractIvyModule {
+    final String ivyPattern
+    final String artifactPattern
+    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"
+    boolean noMetaData
+    int publishCount = 1
+
+    IvyFileModule(String ivyPattern, String artifactPattern, TestFile moduleDir, String organisation, String module, String revision) {
+        this.ivyPattern = ivyPattern
+        this.artifactPattern = artifactPattern
+        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
+     */
+    IvyFileModule artifact(Map<String, ?> options) {
+        artifacts << [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        return this
+    }
+
+    IvyFileModule dependsOn(String organisation, String module, String revision) {
+        dependencies << [organisation: organisation, module: module, revision: revision]
+        return this
+    }
+
+    IvyFileModule dependsOn(String ... modules) {
+        modules.each { dependsOn(organisation, it, revision) }
+        return this
+    }
+
+    IvyFileModule nonTransitive(String config) {
+        configurations[config].transitive = false
+        return this
+    }
+
+    IvyFileModule withStatus(String status) {
+        this.status = status;
+        return this
+    }
+
+    IvyFileModule withNoMetaData() {
+        noMetaData = true;
+        return this
+    }
+
+    TestFile getIvyFile() {
+        def path = IvyPatternHelper.substitute(ivyPattern, ModuleRevisionId.newInstance(organisation, module, revision))
+        return moduleDir.file(path)
+    }
+
+    TestFile getJarFile() {
+        def path = IvyPatternHelper.substitute(artifactPattern, ModuleRevisionId.newInstance(organisation, module, revision), null, "jar", "jar")
+        return moduleDir.file(path)
+    }
+
+    TestFile sha1File(File file) {
+        return moduleDir.file("${file.name}.sha1")
+    }
+
+    TestFile artifactFile(String name) {
+        return file(artifacts.find{ it.name == name })
+    }
+
+    /**
+     * Publishes ivy.xml plus all artifacts with different content to previous publication.
+     */
+    IvyModule publishWithChangedContent() {
+        publishCount++
+        publish()
+    }
+
+    /**
+     * Publishes ivy.xml (if enabled) plus all artifacts
+     */
+    IvyModule publish() {
+        moduleDir.createDir()
+
+        artifacts.each { artifact ->
+            def artifactFile = file(artifact)
+            publish(artifactFile) {
+                artifactFile.text = "${artifactFile.name} : $publishCount"
+            }
+        }
+        if (noMetaData) {
+            return this
+        }
+
+        publish(ivyFile) {
+            ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
+    <!-- ${publishCount} -->
+	<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 ->
+                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 TestFile file(artifact) {
+        return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
+    }
+
+    private publish(File file, Closure cl) {
+        def lastModifiedTime = file.exists() ? file.lastModified() : null
+        cl.call(file)
+        if (lastModifiedTime != null) {
+            file.setLastModified(lastModifiedTime + 2000)
+        }
+        sha1File(file).text = getHash(file, "SHA1")
+    }
+
+    /**
+     * Asserts that exactly the given artifacts have been published.
+     */
+    void assertArtifactsPublished(String... names) {
+        Set allFileNames = []
+        for (name in names) {
+            allFileNames.addAll([name, "${name}.sha1"])
+        }
+        assert moduleDir.list() as Set == allFileNames
+    }
+
+    void assertChecksumPublishedFor(TestFile testFile) {
+        def sha1File = sha1File(testFile)
+        sha1File.assertIsFile()
+        new BigInteger(sha1File.text, 16) == new BigInteger(getHash(testFile, "SHA1"), 16)
+    }
+
+    String getHash(File file, String algorithm) {
+        return HashUtil.createHash(file, algorithm).asHexString()
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy
new file mode 100644
index 0000000..c017f50
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileRepository.groovy
@@ -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.test.fixtures.ivy
+
+import org.apache.ivy.core.IvyPatternHelper
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.util.TestFile
+
+class IvyFileRepository implements IvyRepository {
+    final TestFile rootDir
+
+    IvyFileRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    String getIvyPattern() {
+        return "${uri}/${baseIvyPattern}"
+    }
+
+    String getArtifactPattern() {
+        return "${uri}/${baseArtifactPattern}"
+    }
+
+    String getIvyFilePattern() {
+        "ivy-[revision].xml"
+    }
+
+    String getBaseIvyPattern() {
+        "$dirPattern/$ivyFilePattern"
+    }
+
+    String getArtifactFilePattern() {
+        "[artifact]-[revision](.[ext])"
+    }
+
+    String getBaseArtifactPattern() {
+        "$dirPattern/$artifactFilePattern"
+    }
+
+    String getDirPattern() {
+        "[organisation]/[module]/[revision]"
+    }
+
+    IvyFileModule module(String organisation, String module, Object revision = '1.0') {
+        def revisionString = revision.toString()
+        def path = IvyPatternHelper.substitute(dirPattern, ModuleRevisionId.newInstance(organisation, module, revisionString))
+        def moduleDir = rootDir.file(path)
+        return new IvyFileModule(ivyFilePattern, artifactFilePattern, moduleDir, organisation, module, revisionString)
+    }
+}
+
+
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
new file mode 100644
index 0000000..e6af28c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.ivy
+
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.TestFile
+
+class IvyHttpModule extends AbstractIvyModule {
+    private final IvyFileModule backingModule
+    private final HttpServer server
+    private final String prefix
+
+    IvyHttpModule(HttpServer server, String prefix, IvyFileModule backingModule) {
+        this.prefix = prefix
+        this.server = server
+        this.backingModule = backingModule
+    }
+
+    IvyHttpModule publish() {
+        backingModule.publish()
+        return this
+    }
+
+    IvyHttpModule publishWithChangedContent() {
+        backingModule.publishWithChangedContent()
+        return this
+    }
+
+    IvyHttpModule withNoMetaData() {
+        backingModule.withNoMetaData()
+        return this
+    }
+
+    IvyHttpModule withStatus(String status) {
+        backingModule.withStatus(status)
+        return this
+    }
+
+    IvyHttpModule dependsOn(String organisation, String module, String revision) {
+        backingModule.dependsOn(organisation, module, revision)
+        return this
+    }
+
+    IvyHttpModule artifact(Map<String, ?> options) {
+        backingModule.artifact(options)
+        return this
+    }
+
+    String getIvyFileUri() {
+        return "http://localhost:${server.port}$prefix/$ivyFile.name"
+    }
+
+    TestFile getIvyFile() {
+        return backingModule.ivyFile
+    }
+
+    String getJarFileUri() {
+        return "http://localhost:${server.port}$prefix/$jarFile.name"
+    }
+
+    TestFile getJarFile() {
+        return backingModule.jarFile
+    }
+
+    void allowAll() {
+        server.allowGetOrHead(prefix, backingModule.moduleDir)
+    }
+
+    void expectIvyGet() {
+        server.expectGet("$prefix/$ivyFile.name", ivyFile)
+    }
+
+    void expectIvyGetMissing() {
+        server.expectGetMissing("$prefix/$ivyFile.name")
+    }
+
+    void expectIvyHead() {
+        server.expectHead("$prefix/$ivyFile.name", ivyFile)
+    }
+
+    void expectIvySha1Get() {
+        server.expectGet("$prefix/${ivyFile.name}.sha1", backingModule.sha1File(ivyFile))
+    }
+
+    void expectIvySha1GetMissing() {
+        server.expectGetMissing("$prefix/${ivyFile.name}.sha1")
+    }
+
+    void expectJarGet() {
+        server.expectGet("$prefix/$jarFile.name", jarFile)
+    }
+
+    void expectJarHead() {
+        server.expectHead("$prefix/$jarFile.name", jarFile)
+    }
+
+    void expectJarSha1Get() {
+        server.expectGet("$prefix/${jarFile.name}.sha1", backingModule.sha1File(jarFile))
+    }
+
+    void expectJarSha1GetMissing() {
+        server.expectGetMissing("$prefix/${jarFile.name}.sha1")
+    }
+
+    void expectArtifactGet(String name) {
+        def artifactFile = backingModule.artifactFile(name)
+        server.expectGet("$prefix/$artifactFile.name", artifactFile)
+    }
+
+    void expectPut(String username, String password, File dir, String... artifactNames) {
+        artifactNames.each {
+            server.expectPut("$prefix/$it", username, password, new File(dir, it))
+        }
+    }
+
+}
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy
new file mode 100644
index 0000000..6c76e71
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpRepository.groovy
@@ -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.test.fixtures.ivy
+
+import org.gradle.test.fixtures.server.http.HttpServer
+
+class IvyHttpRepository implements IvyRepository {
+    private final HttpServer server
+    private final IvyFileRepository backingRepository
+    private final String contextPath
+
+    IvyHttpRepository(HttpServer server, String contextPath = "/repo", IvyFileRepository backingRepository) {
+        if (!contextPath.startsWith("/")) {
+            throw new IllegalArgumentException("Context path must start with '/'")
+        }
+        this.contextPath = contextPath
+        this.server = server
+        this.backingRepository = backingRepository
+    }
+
+    URI getUri() {
+        return new URI("http://localhost:${server.port}${contextPath}")
+    }
+
+    String getIvyPattern() {
+        return "$uri/${backingRepository.baseIvyPattern}"
+    }
+
+    String getArtifactPattern() {
+        return "$uri/${backingRepository.baseArtifactPattern}"
+    }
+
+    void expectDirectoryListGet(String organisation, String module) {
+        server.expectGetDirectoryListing("$contextPath/$organisation/$module/", backingRepository.module(organisation, module, "1.0").moduleDir.parentFile)
+    }
+
+    IvyHttpModule module(String organisation, String module, Object revision = "1.0") {
+        return new IvyHttpModule(server, "$contextPath/$organisation/$module/$revision", backingRepository.module(organisation, module, revision))
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
new file mode 100644
index 0000000..324f858
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.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.test.fixtures.ivy;
+
+import org.gradle.util.TestFile;
+
+import java.util.Map;
+
+public interface IvyModule {
+    TestFile getIvyFile();
+
+    TestFile getJarFile();
+
+    /**
+     * Don't publish an ivy.xml for this module.
+     */
+    IvyModule withNoMetaData();
+
+    IvyModule withStatus(String status);
+
+    IvyModule dependsOn(String organisation, String module, String revision);
+
+    IvyModule artifact(Map<String, ?> options);
+
+    /**
+     * Publishes ivy.xml plus all artifacts with different content to previous publication.
+     */
+    IvyModule publishWithChangedContent();
+
+    /**
+     * Publishes ivy.xml plus all artifacts
+     */
+    IvyModule publish();
+
+    IvyDescriptor getIvy();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyRepository.groovy
new file mode 100644
index 0000000..5195945
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyRepository.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.test.fixtures.ivy
+
+interface IvyRepository {
+    URI getUri()
+
+    String getArtifactPattern()
+
+    String getIvyPattern()
+
+    IvyModule module(String organisation, String module)
+
+    IvyModule module(String organisation, String module, Object revision)
+}
+
+
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.groovy
new file mode 100644
index 0000000..5ef6e9e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.groovy
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.test.fixtures.server.http;
+
+
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.handler.AbstractHandler
+import org.mortbay.jetty.handler.HandlerCollection
+
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+public class BlockingHttpServer extends ExternalResource {
+    private final Server server = new Server(0)
+    private final HandlerCollection collection = new HandlerCollection()
+
+    BlockingHttpServer() {
+        def 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
+                }
+                throw new AssertionError("Received unexpected ${request.method} request to ${target}.")
+            }
+        })
+        server.setHandler(handlers)
+    }
+
+    void expectConcurrentExecution(String... expectedCalls) {
+        def handler = new CyclicBarrierRequestHandler(expectedCalls)
+        collection.addHandler(handler)
+    }
+
+    void start() {
+        server.start()
+    }
+
+    void stop() {
+        collection.handlers.each { handler ->
+            handler.assertComplete()
+        }
+        server?.stop()
+    }
+
+    @Override
+    protected void after() {
+        stop()
+    }
+
+    int getPort() {
+        def port = server.connectors[0].localPort
+        if (port < 0) {
+            throw new RuntimeException("""No port available for HTTP server. Still starting perhaps?
+connector: ${server.connectors[0]}
+connector state: ${server.connectors[0].dump()}
+server state: ${server.dump()}
+""")
+        }
+        return port
+    }
+
+    class CyclicBarrierRequestHandler extends AbstractHandler {
+        final Lock lock = new ReentrantLock()
+        final Condition condition = lock.newCondition()
+        final List<String> received = []
+        final Set<String> pending
+
+        CyclicBarrierRequestHandler(String... calls) {
+            pending = calls as Set
+        }
+
+        void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+            if (request.handled) {
+                return
+            }
+
+            Date expiry = new Date(System.currentTimeMillis() + 30000)
+            lock.lock()
+            try {
+                def path = target.replaceFirst('/', '')
+                if (!pending.remove(path)) {
+                    // Unexpected call - let it travel on
+                    return
+                }
+                received.add(path)
+                condition.signalAll()
+                while (!pending.empty) {
+                    if (!condition.awaitUntil(expiry)) {
+                        throw new AssertionError("Timeout waiting for all concurrent requests. Waiting for $pending, received $received.")
+                    }
+                }
+            } finally {
+                lock.unlock()
+            }
+
+            response.addHeader("RESPONSE", "target: done")
+            request.handled = true
+        }
+
+        void assertComplete() {
+            if (!pending.empty) {
+                throw new AssertionError("BlockingHttpServer: did not receive expected concurrent requests. Waiting for $pending, received $received")
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy
new file mode 100755
index 0000000..4457566
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/HttpServer.groovy
@@ -0,0 +1,684 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.test.fixtures.server.http
+
+import org.gradle.test.matchers.UserAgentMatcher
+import org.gradle.util.hash.HashUtil
+import org.hamcrest.Matcher
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.bio.SocketConnector
+import org.mortbay.jetty.handler.AbstractHandler
+import org.mortbay.jetty.handler.HandlerCollection
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.security.Principal
+import java.util.zip.GZIPOutputStream
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+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 TestUserRealm realm
+    private SecurityHandler securityHandler
+    private Connector connector
+    private SslSocketConnector sslConnector
+    AuthScheme authenticationScheme = AuthScheme.BASIC
+
+    private Throwable failure
+    private final List<Expection> expections = []
+    private Matcher expectedUserAgent = null
+
+    enum AuthScheme {
+        BASIC(new BasicAuthHandler()), DIGEST(new DigestAuthHandler())
+
+        final AuthSchemeHandler handler;
+
+        AuthScheme(AuthSchemeHandler handler) {
+            this.handler = handler
+        }
+    }
+
+    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
+                }
+                onFailure(new AssertionError("Received unexpected ${request.method} request to ${target}."))
+                response.sendError(404, "'$target' does not exist")
+            }
+        })
+        server.setHandler(handlers)
+    }
+
+    void start() {
+        start(0)
+    }
+
+    void start(int port) {
+        connector = new SocketConnector()
+        connector.port = port
+        server.addConnector(connector)
+        try {
+            server.start()
+        } catch (java.net.BindException e) {
+            //without this, it is not possible to retry starting the server on the same port
+            //retrying is useful if we need to start server on a specific port
+            //and the OS forces us to wait until it is available.
+            server.removeConnector(connector)
+            throw e
+        }
+    }
+
+    void stop() {
+        resetExpectations()
+        server?.stop()
+        if (connector) {
+            server?.removeConnector(connector)
+        }
+    }
+
+    private void onFailure(Throwable failure) {
+        logger.error(failure.message)
+        if (this.failure == null) {
+            this.failure = failure
+        }
+    }
+
+    void enableSsl(String keyStore, String keyPassword, String trustStore = null, String trustPassword = null) {
+        sslConnector = new SslSocketConnector()
+        sslConnector.keystore = keyStore
+        sslConnector.keyPassword = keyPassword
+        if (trustStore) {
+            sslConnector.needClientAuth = true
+            sslConnector.truststore = trustStore
+            sslConnector.trustPassword = trustPassword
+        }
+        server.addConnector(sslConnector)
+    }
+
+    int getSslPort() {
+        sslConnector.localPort
+    }
+
+    void expectUserAgent(UserAgentMatcher userAgent) {
+        this.expectedUserAgent = userAgent;
+    }
+
+    void resetExpectations() {
+        try {
+            if (failure != null) {
+                throw failure
+            }
+            for (Expection e in expections) {
+                e.assertMet()
+            }
+        } finally {
+            failure = null
+            expectedUserAgent = null
+            expections.clear()
+            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 allowGetOrHead(String path, File srcFile) {
+        allow(path, true, ['GET', 'HEAD'], fileHandler(path, srcFile))
+    }
+
+    /**
+     * Adds a given file at the given URL. The source file can be either a file or a directory.
+     */
+    void allowHead(String path, File srcFile) {
+        allow(path, true, ['HEAD'], fileHandler(path, srcFile))
+    }
+
+    /**
+     * Adds a given file at the given URL with the given credentials. The source file can be either a file or a directory.
+     */
+    void allowGetOrHead(String path, String username, String password, File srcFile) {
+        allow(path, true, ['GET', 'HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
+    }
+
+    private Action fileHandler(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        return new Action() {
+            String getDisplayName() {
+                return "return contents of $srcFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                if (HttpServer.this.expectedUserAgent != null) {
+                    String receivedUserAgent = request.getHeader("User-Agent")
+                    if (!expectedUserAgent.matches(receivedUserAgent)) {
+                        response.sendError(412, String.format("Precondition Failed: Expected User-Agent: '%s' but was '%s'", expectedUserAgent, receivedUserAgent));
+                        return;
+                    }
+                }
+                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, "'$request.pathInfo' does not exist")
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a broken resource at the given URL.
+     */
+    void addBroken(String path) {
+        allow(path, true, null, new Action() {
+            String getDisplayName() {
+                return "return 500 broken"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                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'], notFound())
+    }
+
+    /**
+     * Allows one HEAD request for the given URL, which return 404 status code
+     */
+    void expectHeadMissing(String path) {
+        expect(path, false, ['HEAD'], notFound())
+    }
+
+    private Action notFound() {
+        new Action() {
+            String getDisplayName() {
+                return "return 404 not found"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                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 HEAD request for the given URL with http authentication.
+     */
+    void expectHead(String path, String username, String password, File srcFile, Long lastModified = null, Long contentLength = null) {
+        expect(path, false, ['HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
+    }
+
+    /**
+     * 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 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 Action() {
+            String getDisplayName() {
+                return "return gzipped $srcFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                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 Action() {
+            String getDisplayName() {
+                return "redirect to $location"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                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 Action() {
+            String getDisplayName() {
+                return "return listing of directory $directory.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                sendDirectoryListing(response, directory)
+            }
+        })
+    }
+
+    /**
+     * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
+     */
+    void expectGetDirectoryListing(String path, String username, String password, File directory) {
+        expect(path, false, ['GET'], withAuthentication(path, username, password, new Action() {
+            String getDisplayName() {
+                return "return listing of directory $directory.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                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/html")
+        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 Action() {
+            String getDisplayName() {
+                return "write request to $destFile.name and return status $statusCode"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                if (HttpServer.this.expectedUserAgent != null) {
+                    String receivedUserAgent = request.getHeader("User-Agent")
+                    if (!expectedUserAgent.matches(receivedUserAgent)) {
+                        response.sendError(412, String.format("Precondition Failed: Expected User-Agent: '%s' but was '%s'", expectedUserAgent, receivedUserAgent))
+                        return;
+                    }
+                }
+                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 Action() {
+            String getDisplayName() {
+                return "write request to $destFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+
+                if (request.remoteUser != username) {
+                    response.sendError(500, "unexpected username '${request.remoteUser}'")
+                    return
+                }
+                destFile.bytes = request.inputStream.bytes
+            }
+        }))
+    }
+
+    /**
+     * Allows PUT requests with the given credentials.
+     */
+    void allowPut(String path, String username, String password) {
+        allow(path, false, ['PUT'], withAuthentication(path, username, password, new Action() {
+            String getDisplayName() {
+                return "return 500"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                response.sendError(500, "unexpected username '${request.remoteUser}'")
+            }
+        }))
+    }
+
+    private Action withAuthentication(String path, String username, String password, Action action) {
+        if (realm != null) {
+            assert realm.username == username
+            assert realm.password == password
+            authenticationScheme.handler.addConstraint(securityHandler, path)
+        } else {
+            realm = new TestUserRealm()
+            realm.username = username
+            realm.password = password
+            securityHandler = authenticationScheme.handler.createSecurityHandler(path, realm)
+            collection.addHandler(securityHandler)
+        }
+
+        return new Action() {
+            String getDisplayName() {
+                return action.displayName
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                if (request.remoteUser != username) {
+                    response.sendError(500, "unexpected username '${request.remoteUser}'")
+                    return
+                }
+                action.handle(request, response)
+            }
+        }
+    }
+
+    private void expect(String path, boolean recursive, Collection<String> methods, Action action) {
+        ExpectOne expectation = new ExpectOne(action, methods, path)
+        expections << expectation
+        add(path, recursive, methods, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (expectation.run) {
+                    return
+                }
+                expectation.run = true
+                action.handle(request, response)
+                request.handled = true
+            }
+        })
+    }
+
+    private void allow(String path, boolean recursive, Collection<String> methods, Action action) {
+        add(path, recursive, methods, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                action.handle(request, response)
+                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
+    }
+
+    interface Expection {
+        void assertMet()
+    }
+
+    static class ExpectOne implements Expection {
+        boolean run
+        final Action action
+        final Collection<String> methods
+        final String path
+
+        ExpectOne(Action action, Collection<String> methods, String path) {
+            this.action = action
+            this.methods = methods
+            this.path = path
+        }
+
+        void assertMet() {
+            if (!run) {
+                throw new AssertionError("Expected HTTP request not received: ${methods.size() == 1 ? methods[0] : methods} $path and $action.displayName")
+            }
+        }
+    }
+
+    interface Action {
+        String getDisplayName()
+
+        void handle(HttpServletRequest request, HttpServletResponse response)
+    }
+
+    abstract static class AuthSchemeHandler {
+        public SecurityHandler createSecurityHandler(String path, TestUserRealm realm) {
+            def constraintMapping = createConstraintMapping(path)
+            def securityHandler = new SecurityHandler()
+            securityHandler.userRealm = realm
+            securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
+            securityHandler.authenticator = authenticator
+            return securityHandler
+        }
+
+        public void addConstraint(SecurityHandler securityHandler, String path) {
+            securityHandler.constraintMappings = (securityHandler.constraintMappings as List) + createConstraintMapping(path)
+        }
+
+        private ConstraintMapping createConstraintMapping(String path) {
+            def constraint = new Constraint()
+            constraint.name = constraintName()
+            constraint.authenticate = true
+            constraint.roles = ['*'] as String[]
+            def constraintMapping = new ConstraintMapping()
+            constraintMapping.pathSpec = path
+            constraintMapping.constraint = constraint
+            return constraintMapping
+        }
+
+        protected abstract String constraintName();
+
+        protected abstract Authenticator getAuthenticator();
+    }
+
+    public static class BasicAuthHandler extends AuthSchemeHandler {
+        @Override
+        protected String constraintName() {
+            return Constraint.__BASIC_AUTH
+        }
+
+        @Override
+        protected Authenticator getAuthenticator() {
+            return new BasicAuthenticator()
+        }
+    }
+
+    public static class DigestAuthHandler extends AuthSchemeHandler {
+        @Override
+        protected String constraintName() {
+            return Constraint.__DIGEST_AUTH
+        }
+
+        @Override
+        protected Authenticator getAuthenticator() {
+            return new DigestAuthenticator()
+        }
+    }
+
+    static class TestUserRealm implements UserRealm {
+        String username
+        String password
+
+        Principal authenticate(String username, Object credentials, Request request) {
+            Password passwordCred = new Password(password)
+            if (username == this.username && passwordCred.check(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/test/fixtures/server/http/TestProxyServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TestProxyServer.groovy
new file mode 100644
index 0000000..dd0cebb
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TestProxyServer.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.test.fixtures.server.http
+
+import ch.qos.logback.classic.Level
+import org.gradle.util.AvailablePortFinder
+import org.jboss.netty.handler.codec.http.HttpRequest
+import org.junit.rules.ExternalResource
+import org.slf4j.LoggerFactory
+import org.littleshoot.proxy.*
+
+/**
+ * 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() {
+        // Ignore warnings from this class
+        LoggerFactory.getLogger(HttpRequestHandler).level = Level.ERROR
+
+        port = AvailablePortFinder.createPrivate().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/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy
new file mode 100644
index 0000000..fe95bcc
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/sftp/SFTPServer.groovy
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.server.sftp
+
+import com.jcraft.jsch.JSch
+import com.jcraft.jsch.UserInfo
+import org.apache.commons.io.FileUtils
+import org.apache.sshd.SshServer
+import org.apache.sshd.common.NamedFactory
+import org.apache.sshd.common.Session
+import org.apache.sshd.server.command.ScpCommandFactory
+import org.apache.sshd.server.filesystem.NativeFileSystemFactory
+import org.apache.sshd.server.filesystem.NativeSshFile
+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
+
+import java.security.PublicKey
+
+import org.apache.sshd.server.*
+
+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();
+    }
+
+    static 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;
+        }
+    }
+
+    static abstract class FileRequestLogger {
+        abstract void logRequest(String message)
+    }
+
+    static 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;
+        }
+    }
+
+    static 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)
+                }
+            }
+        }
+    }
+
+
+
+}
+
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/matchers/UserAgentMatcher.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/matchers/UserAgentMatcher.java
new file mode 100644
index 0000000..41edc7f
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/matchers/UserAgentMatcher.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.test.matchers;
+
+import org.hamcrest.*;
+
+public class UserAgentMatcher extends BaseMatcher {
+
+    private final String applicationName;
+    private final String version;
+
+    public UserAgentMatcher(String applicationName, String version) {
+        this.applicationName = applicationName;
+        this.version = version;
+    }
+
+    public void describeTo(Description description) {
+        description.appendValue(expectedUserAgentString());
+    }
+
+    @Factory
+    public static Matcher matchesNameAndVersion(String applicationName, String version) {
+        return new UserAgentMatcher(applicationName, version);
+    }
+
+    public boolean matches(Object o) {
+        String testString = expectedUserAgentString();
+        return Matchers.equalTo(testString).matches(o);
+    }
+
+    private String expectedUserAgentString() {
+        String javaVendor = System.getProperty("java.vendor");
+        String javaVersion = System.getProperty("java.version");
+        String javaVendorVersion = System.getProperty("java.vm.version");
+        String osName = System.getProperty("os.name");
+        String osVersion = System.getProperty("os.version");
+        String osArch = System.getProperty("os.arch");
+        return String.format("%s/%s (%s;%s;%s) (%s;%s;%s)", applicationName, version, osName, osVersion, osArch, javaVendor, javaVersion, javaVendorVersion);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/resources/logback.xml b/subprojects/internal-integ-testing/src/main/resources/logback.xml
new file mode 100644
index 0000000..dc5db3a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- encoders are assigned the type
+     ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="warn">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
diff --git a/subprojects/internal-testing/internal-testing.gradle b/subprojects/internal-testing/internal-testing.gradle
index 001ad89..751f5e2 100644
--- a/subprojects/internal-testing/internal-testing.gradle
+++ b/subprojects/internal-testing/internal-testing.gradle
@@ -17,8 +17,6 @@
 /*
     Provides generally useful test utilities, used for unit and integration testing.
 */
-apply from: "$rootDir/gradle/classycle.gradle"
-
 dependencies {
     groovy libraries.groovy
 
@@ -31,4 +29,6 @@ dependencies {
     compile libraries.jmock
     compile libraries.spock
     compile libraries.jsr305 //it is a guava dependency needed for compilation on ibm vm / windows
-}
\ No newline at end of file
+}
+
+useClassycle()
\ No newline at end of file
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
index 44dc2c5..064acb8 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
@@ -417,10 +417,10 @@ public class TestFile extends File implements TestFileContext {
     }
 
     public TestFile createDir() {
-        if (isDirectory()) {
+        if (mkdirs()) {
             return this;
         }
-        if (mkdirs()) {
+        if (isDirectory()) {
             return this;
         }
         throw new AssertionError("Problems creating dir: " + this
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
index 6320835..1a7c050 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
@@ -187,6 +187,9 @@ class TestFileHelper {
             zip.setBasedir(file);
             zip.setDestFile(zipFile);
             zip.setProject(new Project());
+            def whenEmpty = new Zip.WhenEmpty()
+            whenEmpty.setValue("create")
+            zip.setWhenempty(whenEmpty);
             zip.execute();
         }
     }
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
index 980b7bc..5614ddc 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -75,6 +75,9 @@ enum TestPrecondition {
     UNKNOWN_OS({
         OperatingSystem.current().name == "unknown operating system"
     }),
+    NOT_UNKNOWN_OS({
+        !UNKNOWN_OS.fulfilled
+    }),
     JDK5({
         System.getProperty("java.version").startsWith("1.5")
     }),
diff --git a/subprojects/ivy/ivy.gradle b/subprojects/ivy/ivy.gradle
new file mode 100644
index 0000000..197b993
--- /dev/null
+++ b/subprojects/ivy/ivy.gradle
@@ -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.
+ */
+
+
+
+dependencies {
+    groovy libraries.groovy
+    compile project(':core'),
+            project(':publish'),
+            project(':plugins') // for base plugin to get archives conf
+
+    integTestCompile project(":ear")
+}
+
+useTestFixtures()
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.groovy
new file mode 100644
index 0000000..3483d6f
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.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.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+class AutoTestedSamplesIvyIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/ivy/src/main")
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
new file mode 100644
index 0000000..9d95829
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IvyCustomPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    public void "can publish custom configurations"() {
+        given:
+        def module = ivyRepo.module("org.gradle", "publish", "2")
+        def artifact = file("artifact.txt").createFile()
+
+        settingsFile << 'rootProject.name = "publish"'
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            configurations { custom }
+            artifacts {
+                custom file("${artifact.name}"), {
+                    name "foo"
+                    extension "txt"
+                }
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertArtifactsPublished("ivy-2.xml", "foo-2.txt", "publish-2.jar")
+        with(module.ivy.artifacts.foo) {
+            name == "foo"
+            ext == "txt"
+            "custom" in conf
+        }
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..cf935d8
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
@@ -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.publish.ivy
+
+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'
+            apply plugin: 'ivy-publish'
+
+            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"
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.ear", "publishTest-1.9.jar")
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
new file mode 100644
index 0000000..e9c4c2c
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyFileModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
+import org.gradle.util.Jvm
+import org.gradle.util.TestFile
+import org.gradle.util.TextUtil
+import org.hamcrest.Matchers
+import org.junit.Rule
+import org.mortbay.jetty.HttpStatus
+import spock.lang.Unroll
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+
+public class IvyHttpPublishIntegrationTest extends AbstractIntegrationSpec {
+    private static final String BAD_CREDENTIALS = '''
+credentials {
+    username 'testuser'
+    password 'bad'
+}
+'''
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    @Rule ProgressLoggingFixture progressLogging
+
+    private IvyFileModule module
+
+    def setup() {
+        module = ivyRepo.module("org.gradle", "publish", "2")
+        module.moduleDir.mkdirs()
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
+    }
+
+    public void canPublishToUnauthenticatedHttpRepository() {
+        given:
+        server.start()
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "http://localhost:${server.port}"
+                    }
+                }
+            }
+        """
+
+        when:
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, HttpStatus.ORDINAL_201_Created)
+
+        and:
+        succeeds 'publish'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
+    }
+
+
+    @Unroll
+    def "can publish to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        credentials {
+                            username 'testuser'
+                            password 'password'
+                        }
+                        url "http://localhost:${server.port}"
+                    }
+                }
+            }
+        """
+
+        when:
+        server.authenticationScheme = authScheme
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure publishing with #credsName credentials to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+            version = '2'
+            group = 'org.gradle'
+            publishing {
+                repositories {
+                    ivy {
+                        $creds
+                        url "http://localhost:${server.port}"
+                    }
+                }
+            }
+        """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowPut('/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme                   | credsName | creds
+        HttpServer.AuthScheme.BASIC  | 'empty'   | ''
+        HttpServer.AuthScheme.DIGEST | 'empty'   | ''
+        HttpServer.AuthScheme.BASIC  | 'bad'     | BAD_CREDENTIALS
+        HttpServer.AuthScheme.DIGEST | 'bad'     | BAD_CREDENTIALS
+    }
+
+    public void reportsFailedPublishToHttpRepository() {
+        given:
+        server.start()
+        def repositoryUrl = "http://localhost:${server.port}"
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${repositoryUrl}"
+                    }
+                }
+            }
+        """
+
+        when:
+        server.addBroken("/")
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
+
+        when:
+        server.stop()
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Could not publish configurations: [archives, compile, default, runtime]')
+        failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
+    }
+
+    public void usesFirstConfiguredPatternForPublication() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+            publishing {
+                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"
+                    }
+                }
+            }
+        """
+
+        when:
+        expectUpload('/primary/publish/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/primary-ivy/publish/ivy-2.xml', module, module.ivyFile)
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+    }
+
+    public void "can publish large artifact (tools.jar) to authenticated repository"() {
+        given:
+        server.start()
+        def toolsJar = TextUtil.escapeString(Jvm.current().toolsJar)
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'base'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            configurations {
+                archives
+            }
+
+            artifacts {
+                archives(file('$toolsJar')) {
+                    name 'tools'
+                }
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        credentials {
+                            username 'testuser'
+                            password 'password'
+                        }
+                        url "http://localhost:${server.port}"
+                    }
+                }
+            }
+        """
+
+        when:
+        def uploadedToolsJar = module.moduleDir.file('toolsJar')
+        expectUpload('/org.gradle/publish/2/tools-2.jar', module, uploadedToolsJar, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        uploadedToolsJar.assertIsCopyOf(new TestFile(toolsJar));
+
+    }
+
+    public void "does not upload meta-data file if artifact upload fails"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "http://localhost:${server.port}"
+                    }
+                }
+            }
+        """
+        when:
+        server.expectPut("/org.gradle/publish/2/publish-2.jar", module.jarFile, HttpStatus.ORDINAL_500_Internal_Server_Error)
+
+        then:
+        fails ':publish'
+
+        and:
+        module.jarFile.assertExists()
+        module.ivyFile.assertDoesNotExist()
+    }
+
+    private void expectUpload(String path, IvyFileModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
+        server.expectPut(path, file, statusCode)
+        server.expectPut("${path}.sha1", module.sha1File(file), statusCode)
+    }
+
+    private void expectUpload(String path, IvyFileModule module, TestFile file, String username, String password) {
+        server.expectPut(path, username, password, file)
+        server.expectPut("${path}.sha1", username, password, module.sha1File(file))
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..da10db4
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+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'
+            apply plugin: 'ivy-publish'
+
+            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"
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
+        ivyModule.ivy.dependencies.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        ivyModule.ivy.dependencies.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
new file mode 100644
index 0000000..978fa46
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
@@ -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.
+ */
+
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.spockframework.util.TextUtil
+import spock.lang.Ignore
+import spock.lang.Issue
+
+public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void canPublishToLocalFileRepository() {
+        given:
+        def module = ivyRepo.module("org.gradle", "publish", "2")
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+    }
+
+    @Issue("GRADLE-2456")
+    public void generatesSHA1FileWithLeadingZeros() {
+        given:
+        def module = ivyRepo.module("org.gradle", "publish", "2")
+        byte[] jarBytes = [0, 0, 0, 5]
+        def artifactFile = file("testfile.bin")
+        artifactFile << jarBytes
+        def artifactPath = TextUtil.escape(artifactFile.path)
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            group = "org.gradle"
+            version = '2'
+
+            artifacts {
+                archives file: file("${artifactPath}"), name: 'testfile', type: 'bin'
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def shaOneFile = module.moduleDir.file("testfile-2.bin.sha1")
+        shaOneFile.exists()
+        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
+
+    @Ignore("There's no real parallel of this with the new publication mechanism. Eventually, descriptor generation will be a standalone task")
+    @Issue("GRADLE-1811")
+    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:
+        buildFile << '''
+            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:
+        succeeds 'ivyXml'
+
+        then:
+        file('ivy.xml').assertIsFile()
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
new file mode 100644
index 0000000..fdafb45
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
@@ -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.publish.ivy
+
+import groovy.util.slurpersupport.GPathResult
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IvyPublishDescriptorModificationIntegTest extends AbstractIntegrationSpec {
+
+    def module = ivyRepo.module("org.gradle", "publish", "2")
+
+    def setup() {
+        settingsFile << """
+            rootProject.name = "${module.module}"
+        """
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '${module.revision}'
+            group = '${module.organisation}'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+            }
+        """
+    }
+
+    def "can modify descriptor during publication"() {
+        when:
+        succeeds 'publish'
+
+        then:
+        ":jar" in executedTasks
+
+        and:
+        asXml(module.ivyFile).info[0]. at revision == "2"
+
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor {
+                            withXml {
+                                asNode().info[0]. at revision = "3"
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        succeeds 'publish'
+
+
+        then:
+        ":jar" in skippedTasks
+
+        and:
+        // Note that the modified “coordinates” do not affect how the module is published
+        // This is intentional
+        asXml(module.ivyFile).info[0]. at revision == "3"
+    }
+
+    GPathResult asXml(File file) {
+        new XmlSlurper().parse(file)
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
new file mode 100644
index 0000000..34c9dc6
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.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.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.test.fixtures.ivy.IvyModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+class IvyPublishMultipleReposIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule HttpServer server
+    @Rule ProgressLoggingFixture progressLogging
+
+    String moduleName = "publish"
+    String org = "org.gradle"
+    String rev = "2"
+
+    IvyFileRepository repo1 = new IvyFileRepository(file("repo1"))
+    IvyModule repo1Module = repo1.module(org, moduleName, rev)
+
+    IvyFileRepository repo2 = new IvyFileRepository(file("repo2"))
+    IvyModule repo2Module = repo2.module(org, moduleName, rev)
+
+    def "can publish to different repositories"() {
+        given:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                publications {
+                    ivy.descriptor.withXml {
+                        asNode(). at rev = 10
+                    }
+                }
+                repositories {
+                    ivy {
+                        url "${repo1.uri}"
+                    }
+                    ivy {
+                        name "repo2"
+                        url "${repo2.uri}"
+                    }
+                }
+            }
+
+            // Be nasty and delete the descriptor after the first publishing
+            // to make sure it's regenerated for the second publish
+            publishIvyPublicationToIvyRepository {
+                doLast {
+                    assert publication.descriptor.file.delete()
+                    publication.descriptor.withXml { asNode(). at rev = "11" }
+                }
+            }
+        """
+
+        when:
+        succeeds "publish"
+
+        then:
+        ":publishIvyPublicationToIvyRepository" in executedTasks
+        ":publishIvyPublicationToRepo2Repository" in executedTasks
+
+        and:
+        repo1Module.ivyFile.exists()
+        repo1Module.jarFile.exists()
+        repo2Module.ivyFile.exists()
+        repo2Module.jarFile.exists()
+
+        and: // Modification applied to both
+        repo1Module.ivy.rev == "10"
+        repo2Module.ivy.rev == "11"
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.groovy
new file mode 100644
index 0000000..e7a9052
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.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.api.publish.ivy
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class IvyPublishPluginIntegTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "ivy-publish"
+    }
+
+    @Override
+    String getMainTask() {
+        "publish"
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy
new file mode 100644
index 0000000..ad8bf6f
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySFtpPublishIntegrationTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ProgressLoggingFixture
+import org.gradle.test.fixtures.server.sftp.SFTPServer
+import org.junit.Rule
+import spock.lang.Ignore
+
+ at Ignore("New publishing requires IvyArtifactRepository, which doesn't allow custom resolvers")
+class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final SFTPServer sftpServer = new SFTPServer(distribution.temporaryFolder)
+    @Rule
+    ProgressLoggingFixture progressLogging
+
+    public void "can publish using SftpResolver"() {
+        given:
+        file("settings.gradle") << 'rootProject.name = "publish"'
+
+        and:
+        buildFile << """
+        apply plugin: 'java'
+        version = '2'
+        group = 'org.gradle'
+
+        publishing {
+            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:
+        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'))
+        and:
+        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/ivy-2.xml")
+        progressLogging.uploadProgressLogged("repos/libs/org.gradle/publish/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/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..91dab6f
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Ignore
+
+class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    def "publish multiple artifacts in single configuration"() {
+        settingsFile << "rootProject.name = 'publishTest'"
+        file("file1") << "some content"
+        file("file2") << "other content"
+
+        buildFile << """
+            apply plugin: "base"
+            apply plugin: "ivy-publish"
+
+            group = "org.gradle.test"
+            version = 1.9
+
+            configurations {
+                toPublish.visible = false
+                archives.extendsFrom toPublish
+            }
+
+            task jar1(type: Jar) {
+                baseName = "jar1"
+                from "file1"
+            }
+
+            task jar2(type: Jar) {
+                baseName = "jar2"
+                from "file2"
+            }
+
+            artifacts {
+                toPublish jar1, jar2
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar1-1.9.jar", "jar2-1.9.jar")
+        ivyModule.moduleDir.file("jar1-1.9.jar").bytes == file("build/libs/jar1-1.9.jar").bytes
+        ivyModule.moduleDir.file("jar2-1.9.jar").bytes == file("build/libs/jar2-1.9.jar").bytes
+
+        and:
+        def ivyDescriptor = ivyModule.ivy
+        ivyDescriptor.expectArtifact("jar1").conf == ["archives", "toPublish"]
+        ivyDescriptor.expectArtifact("jar2").conf == ["toPublish"]
+    }
+
+    @Ignore("We don't have a way to have separate publications right now")
+    def "publish multiple artifacts in separate configurations"() {
+        file("settings.gradle") << "rootProject.name = 'publishTest'"
+        file("file1") << "some content"
+        file("file2") << "other content"
+
+        buildFile << """
+            apply plugin: "base"
+
+            group = "org.gradle.test"
+            version = 1.9
+
+            configurations { publish1; publish2 }
+
+            task jar1(type: Jar) {
+                baseName = "jar1"
+                from "file1"
+            }
+
+            task jar2(type: Jar) {
+                baseName = "jar2"
+                from "file2"
+            }
+
+            artifacts {
+                publish1 jar1
+                publish2 jar2
+            }
+
+            tasks.withType(Upload) {
+                repositories {
+                    main {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        run "uploadPublish$n"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar$n-1.9.jar")
+        ivyModule.moduleDir.file("jar$n-1.9.jar").bytes == file("build/libs/jar$n-1.9.jar").bytes
+
+        and:
+        def ivyDescriptor = ivyModule.ivy
+        ivyDescriptor.expectArtifact("jar$n").conf.contains("publish$n" as String)
+        ivyDescriptor.expectArtifact("jar$n").conf.contains("archives") == onArchivesConfig
+
+        where:
+        n | onArchivesConfig
+        1 | true
+        2 | false
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..6ccd73d
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
@@ -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.api.publish.ivy
+
+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'
+            apply plugin: 'ivy-publish'
+
+            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"
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then:
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.war", "publishTest-1.9.jar")
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
new file mode 100644
index 0000000..0c43953
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.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.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.ivy.IvyDescriptor
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.ivy.IvyModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.TestFile
+import org.gradle.util.TextUtil
+import org.junit.Rule
+
+public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule Sample sample = new Sample("ivypublish-new")
+    @Rule HttpServer httpServer
+
+    def sample() {
+        given:
+        httpServer.start()
+        executer.inDirectory(sample.dir)
+
+        and:
+        def fileRepo = new IvyFileRepository(new TestFile(sample.dir, "repo"))
+        IvyHttpRepository httpRepo = new IvyHttpRepository(httpServer, fileRepo)
+        IvyModule ivyModule = httpRepo.module("org.gradle.test", "ivypublish", "1.0")
+        def uploads = file("uploads").createDir()
+        ivyModule.expectPut("user1", "secret", uploads,
+                "ivypublish-1.0.jar", "ivypublish-1.0.jar.sha1",
+                "ivypublishSource-1.0-src.jar", "ivypublishSource-1.0-src.jar.sha1",
+                "ivy-1.0.xml", "ivy-1.0.xml.sha1"
+        )
+
+        and:
+        sample.dir.file("build.gradle") << """
+            publishing.repositories.ivy.url = "${httpRepo.uri}"
+        """
+
+        when:
+        succeeds "publish"
+
+        then:
+        def uploadedDescriptor = uploads.file("ivy-1.0.xml")
+        IvyDescriptor ivy = new IvyDescriptor(uploadedDescriptor)
+        ivy.artifacts.ivypublishSource.mavenAttributes.classifier == "src"
+        ivy.configurations.keySet() == ['archives', 'compile', 'default', 'runtime', 'testCompile', 'testRuntime'] as Set
+        ivy.dependencies.compile.assertDependsOn('junit', 'junit', '4.10')
+        ivy.dependencies.compile.assertDependsOn('ivypublish', 'subproject', 'unspecified')
+
+        def actualIvyXmlText = uploadedDescriptor.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
+        actualIvyXmlText == expectedIvyOutput
+    }
+
+    String getExpectedIvyOutput() {
+        sample.dir.file("output-ivy.xml").readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
new file mode 100644
index 0000000..3dfef9f
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.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.publish.ivy;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.HasInternalProtocol;
+
+import java.io.File;
+
+/**
+ * The descriptor of any Ivy publication.
+ * <p>
+ * Corresponds to the <a href="http://ant.apache.org/ivy/history/latest-milestone/ivyfile.html">XML version of the Ivy Module Descriptor</a>.
+ * <p>
+ * The {@link #withXml(org.gradle.api.Action)} method can be used to modify the descriptor after it has been generated according to the publication data.
+ *
+ * @since 1.3
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface IvyModuleDescriptor {
+
+    /**
+     * Allow configuration of the descriptor, after it has been generated according to the input data.
+     *
+     * <pre autoTested="">
+     * apply plugin: "ivy-publish"
+     *
+     * publishing {
+     *   publications {
+     *     ivy {
+     *       descriptor {
+     *         withXml {
+     *           asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
+     *         }
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * Note that due to Gradle's internal type conversion system, you can pass a Groovy closure to this method and
+     * it will be automatically converted to an {@code Action}.
+     * <p>
+     * Each action/closure passed to this method will be stored as a callback, and executed when the publication
+     * that this descriptor is attached to is published.
+     * <p>
+     * For details on the structure of the XML to be modified, see <a href="http://ant.apache.org/ivy/history/latest-milestone/ivyfile.html">the
+     * Ivy Module Descriptor reference</a>.
+     *
+     * @param action The configuration action.
+     * @see IvyPublication
+     * @see XmlProvider
+     */
+    void withXml(Action<XmlProvider> action);
+
+    /**
+     * The generated descriptor file.
+     *
+     * This file will only exist <b>after</b> the publishing task that publishing the publication this descriptor is part of.
+     *
+     * @return The generated descriptor file
+     */
+    File getFile();
+
+    /**
+     * Sets where the descriptor file should be generated.
+     *
+     * @param descriptorFile The new location to generate the descriptor to
+     */
+    void setFile(File descriptorFile);
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
new file mode 100644
index 0000000..3fa0313
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.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.publish.ivy;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.internal.HasInternalProtocol;
+import org.gradle.api.publish.Publication;
+
+/**
+ * An {@code IvyPublication} is the representation/configuration of how Gradle should publish something in Ivy format.
+ *
+ * <h3>The “{@code ivy-publish}” plugin and the default publication</h3>
+ *
+ * The “{@code ivy-publish}” plugin creates one {@code IvyPublication} named “{@code ivy}” in the project's
+ * {@code publishing.publications} container. This publication is configured to publish all of the project's
+ * <i>visible</i> configurations (i.e. {@link org.gradle.api.Project#getConfigurations()}).
+ * <p>
+ * The Ivy module identifying attributes of the publication are mapped to:
+ * <ul>
+ * <li>{@code module} - {@code project.name}</li>
+ * <li>{@code organisation} - {@code project.group}</li>
+ * <li>{@code revision} - {@code project.version}</li>
+ * <li>{@code status} - {@code project.status}</li>
+ * </ul>
+ * <p>
+ * The ability to add multiple publications and finely configure publications will be added in future Gradle versions.
+ *
+ * <h3>Publishing the publication</h3>
+ *
+ * The “{@code ivy-publish}” plugin will automatically create a {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository} task
+ * for each {@code IvyPublication} and {@link org.gradle.api.artifacts.repositories.IvyArtifactRepository} combination in
+ * {@code publishing.publications} and {@code publishing.repositories} respectively.
+ * <p>
+ * Given the following…
+ * <pre autoTested="true">
+ * apply plugin: 'ivy-publish'
+ *
+ * publishing {
+ *   repositories {
+ *     ivy { url "http://my.org/repo1" }
+ *     ivy {
+ *       name "other"
+ *       url "http://my.org/repo2"
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * The following tasks will be created automatically by the plugin:
+ *
+ * <ul>
+ * <li>{@code publishIvyPublicationToIvyRepository} - publishes to the first repository (repository default name is “{@code ivy}”) defined</li>
+ * <li>{@code publishIvyPublicationToOtherRepository} - publishes to the second repository defined</li>
+ * </ul>
+ *
+ * These tasks are of type {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository}. Executing the task will publish the publication
+ * to the associated repository.
+ *
+ * <h4>The “{@code publish}” task</h4>
+ *
+ * The “{@code publish}” plugin (that the “{@code ivy-publish}” plugin implicitly applies) adds a lifecycle task named “{@code publish}”.
+ * All {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository} tasks added by this plugin automatically become dependencies of this
+ * lifecycle task, which means that often the most convenient way to publish your project is to just run the “{@code publish}” task.
+ *
+ * @since 1.3
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface IvyPublication extends Publication {
+
+    /**
+     * The module descriptor that will be published.
+     * <p>
+     *
+     * @return The module descriptor that will be published.
+     */
+    IvyModuleDescriptor getDescriptor();
+
+    /**
+     * Configures the descriptor that will be published.
+     * <p>
+     * The descriptor XML can be modified by using the {@link IvyModuleDescriptor#withXml(org.gradle.api.Action)} method.
+     *
+     * @param configure The configuration action.
+     */
+    void descriptor(Action<? super IvyModuleDescriptor> configure);
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
new file mode 100644
index 0000000..5559017
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.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.publish.ivy.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.XmlTransformer;
+
+import java.io.File;
+
+public class DefaultIvyModuleDescriptor implements IvyModuleDescriptorInternal {
+
+    private final XmlTransformer transformer = new XmlTransformer();
+    private File file;
+
+    public void withXml(Action<XmlProvider> action) {
+        transformer.addAction(action);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File descriptorFile) {
+        this.file = descriptorFile;
+    }
+
+    public XmlTransformer getTransformer() {
+        return transformer;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
new file mode 100644
index 0000000..54543fb
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.publish.ivy.IvyModuleDescriptor;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.gradle.util.CollectionUtils.*;
+
+public class DefaultIvyPublication implements IvyPublicationInternal {
+
+    private final String name;
+    private final IvyModuleDescriptorInternal descriptor;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+    private final Set<? extends Configuration> configurations;
+    private final FileResolver fileResolver;
+    private final TaskResolver taskResolver;
+
+    public DefaultIvyPublication(
+            String name, Instantiator instantiator, Set<? extends Configuration> configurations,
+            DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver, TaskResolver taskResolver
+    ) {
+        this.name = name;
+        this.descriptor = instantiator.newInstance(DefaultIvyModuleDescriptor.class);
+        this.configurations = configurations;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+        this.fileResolver = fileResolver;
+        this.taskResolver = taskResolver;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public IvyModuleDescriptorInternal getDescriptor() {
+        return descriptor;
+    }
+
+    public void descriptor(Action<? super IvyModuleDescriptor> configure) {
+        configure.execute(descriptor);
+    }
+
+    public FileCollection getPublishableFiles() {
+        return new DefaultConfigurableFileCollection(
+                "publication artifacts", fileResolver, taskResolver,
+                collect(configurations, new Transformer<FileCollection, Configuration>() {
+                    public FileCollection transform(Configuration configuration) {
+                        return configuration.getAllArtifacts().getFiles();
+                    }
+                }));
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return getPublishableFiles().getBuildDependencies();
+    }
+
+    public IvyNormalizedPublication asNormalisedPublication() {
+        return new IvyNormalizedPublication(dependencyMetaDataProvider.getModule(), getFlattenedConfigurations(), descriptor.getFile(), descriptor.getTransformer());
+    }
+
+    public Class<IvyNormalizedPublication> getNormalisedPublicationType() {
+        return IvyNormalizedPublication.class;
+    }
+
+    // Flattens each of the given configurations to include any parents, visible or not.
+    private Set<Configuration> getFlattenedConfigurations() {
+        return inject(new HashSet<Configuration>(), configurations, new Action<InjectionStep<Set<Configuration>, Configuration>>() {
+            public void execute(InjectionStep<Set<Configuration>, Configuration> step) {
+                step.getTarget().addAll(step.getItem().getHierarchy());
+            }
+        });
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
new file mode 100644
index 0000000..bcfb751
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.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.publish.ivy.internal;
+
+import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.publish.ivy.IvyModuleDescriptor;
+
+public interface IvyModuleDescriptorInternal extends IvyModuleDescriptor {
+
+    XmlTransformer getTransformer();
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
new file mode 100644
index 0000000..b2fbb1e
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.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.publish.ivy.internal;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.ivy.IvyPublication;
+
+public interface IvyPublicationInternal extends IvyPublication, Buildable {
+
+    IvyModuleDescriptorInternal getDescriptor();
+
+    FileCollection getPublishableFiles();
+
+    IvyNormalizedPublication asNormalisedPublication();
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/package-info.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/package-info.java
new file mode 100644
index 0000000..de447ac
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/package-info.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.
+ */
+
+/**
+ * Types that deal with publishing in the Ivy format.
+ *
+ * @since 1.3
+ */
+ at Incubating
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java
new file mode 100644
index 0000000..c57fc04
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/IvyPublishPlugin.java
@@ -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.publish.ivy.plugins;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.ivy.IvyPublication;
+import org.gradle.api.publish.ivy.internal.DefaultIvyPublication;
+import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal;
+import org.gradle.api.publish.ivy.tasks.internal.IvyPublishDynamicTaskCreator;
+import org.gradle.api.publish.plugins.PublishingPlugin;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Configures the project to publish a “main” IvyPublication to a “main” IvyArtifactRepository.
+ *
+ * Creates an IvyPublication named "main" in project.publications, configured to publish all of the visible configurations of the project.
+ * Creates an IvyArtifactRepository
+ *
+ * @since 1.3
+ */
+ at Incubating
+public class IvyPublishPlugin implements Plugin<Project> {
+
+    private final Instantiator instantiator;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+    private final FileResolver fileResolver;
+
+    @Inject
+    public IvyPublishPlugin(
+            Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver
+    ) {
+        this.instantiator = instantiator;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+        this.fileResolver = fileResolver;
+    }
+
+    public void apply(Project project) {
+        project.getPlugins().apply(PublishingPlugin.class);
+        PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
+
+        // Create the default publication
+        Set<Configuration> visibleConfigurations = project.getConfigurations().matching(new Spec<Configuration>() {
+            public boolean isSatisfiedBy(Configuration configuration) {
+                return configuration.isVisible();
+            }
+        });
+        extension.getPublications().add(createPublication("ivy", project, visibleConfigurations));
+
+        // Create publish tasks automatically for any Ivy publication and repository combinations
+        Task publishLifecycleTask = project.getTasks().getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
+        IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(project.getTasks(), publishLifecycleTask);
+        publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
+    }
+
+    private IvyPublication createPublication(String name, final Project project, Set<? extends Configuration> configurations) {
+        final DefaultIvyPublication publication = instantiator.newInstance(
+                DefaultIvyPublication.class,
+                name, instantiator, configurations, dependencyMetaDataProvider, fileResolver, project.getTasks()
+        );
+
+        IvyModuleDescriptorInternal descriptor = publication.getDescriptor();
+        DslObject descriptorDslObject = new DslObject(descriptor);
+        ConventionMapping descriptorConventionMapping = descriptorDslObject.getConventionMapping();
+        descriptorConventionMapping.map("file", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
+            }
+        });
+
+        return publication;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/package-info.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/package-info.java
new file mode 100644
index 0000000..c548f9a
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/plugins/package-info.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.
+ */
+
+/**
+ * Plugins for Ivy publishing.
+ *
+ * @since 1.3
+ */
+ at Incubating
+package org.gradle.api.publish.ivy.plugins;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
new file mode 100644
index 0000000..0a0bd92
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal;
+import org.gradle.api.publish.ivy.IvyPublication;
+import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication;
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.internal.IvyPublisher;
+import org.gradle.api.tasks.TaskAction;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Publishes an IvyPublication to an IvyArtifactRepository.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public class PublishToIvyRepository extends DefaultTask {
+
+    private IvyPublicationInternal publication;
+    private IvyArtifactRepositoryInternal repository;
+
+    public PublishToIvyRepository() {
+        // Allow the publication to participate in incremental build
+        getInputs().files(new Callable<FileCollection>() {
+            public FileCollection call() throws Exception {
+                IvyPublicationInternal publicationInternal = getPublicationInternal();
+                return publicationInternal == null ? null : publicationInternal.getPublishableFiles();
+            }
+        });
+
+        // Allow the publication to have its dependencies fulfilled
+        // There may be dependencies that aren't about creating files and not covered above
+        dependsOn(new Callable<Buildable>() {
+            public Buildable call() throws Exception {
+                IvyPublicationInternal publicationInternal = getPublicationInternal();
+                return publicationInternal == null ? null : publicationInternal;
+            }
+        });
+
+        // Should repositories be able to participate in incremental?
+        // At the least, they may be able to express themselves as output files
+        // They *might* have input files and other dependencies as well though
+        // Inputs: The credentials they need may be expressed in a file
+        // Dependencies: Can't think of a case here
+    }
+
+    /**
+     * The publication to be published.
+     *
+     * @return The publication to be published
+     */
+    public IvyPublication getPublication() {
+        return publication;
+    }
+
+    /**
+     * Sets the publication to be published.
+     *
+     * @param publication The publication to be published
+     */
+    public void setPublication(IvyPublication publication) {
+        this.publication = toPublicationInternal(publication);
+    }
+
+    private IvyPublicationInternal getPublicationInternal() {
+        return toPublicationInternal(getPublication());
+    }
+
+    private static IvyPublicationInternal toPublicationInternal(IvyPublication publication) {
+        if (publication == null) {
+            return null;
+        } else if (publication instanceof IvyPublicationInternal) {
+            return (IvyPublicationInternal) publication;
+        } else {
+            throw new InvalidUserDataException(
+                    String.format(
+                            "publication objects must implement the '%s' interface, implementation '%s' does not",
+                            IvyPublicationInternal.class.getName(),
+                            publication.getClass().getName()
+                    )
+            );
+        }
+    }
+
+    /**
+     * The repository to publish to.
+     *
+     * @return The repository to publish to
+     */
+    public IvyArtifactRepository getRepository() {
+        return repository;
+    }
+
+    /**
+     * Sets the repository to publish to.
+     *
+     * @param repository The repository to publish to
+     */
+    public void setRepository(IvyArtifactRepository repository) {
+        this.repository = toRepositoryInternal(repository);
+    }
+
+    private IvyArtifactRepositoryInternal getRepositoryInternal() {
+        return toRepositoryInternal(getRepository());
+    }
+
+    private static IvyArtifactRepositoryInternal toRepositoryInternal(IvyArtifactRepository repository) {
+        if (repository == null) {
+            return null;
+        } else if (repository instanceof IvyArtifactRepositoryInternal) {
+            return (IvyArtifactRepositoryInternal) repository;
+        } else {
+            throw new InvalidUserDataException(
+                    String.format(
+                            "repository objects must implement the '%s' interface, implementation '%s' does not",
+                            IvyArtifactRepositoryInternal.class.getName(),
+                            repository.getClass().getName()
+                    )
+            );
+        }
+    }
+
+    @TaskAction
+    public void publish() {
+        IvyPublicationInternal publicationInternal = getPublicationInternal();
+        if (publicationInternal == null) {
+            throw new InvalidUserDataException("The 'publication' property is required");
+        }
+
+        IvyArtifactRepositoryInternal repositoryInternal = getRepositoryInternal();
+        if (repositoryInternal == null) {
+            throw new InvalidUserDataException("The 'repository' property is required");
+        }
+
+        doPublish(publicationInternal, repositoryInternal);
+    }
+
+    private void doPublish(IvyPublicationInternal publication, IvyArtifactRepositoryInternal repository) {
+        IvyPublisher publisher = repository.createPublisher();
+        IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
+        publisher.publish(normalizedPublication);
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
new file mode 100644
index 0000000..e559b52
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal;
+import org.gradle.api.publish.Publication;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+/**
+ * Dynamically creates tasks for each Ivy publication/repository pair in a publication set and repository set.
+ */
+public class IvyPublishDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishLifecycleTask;
+
+    public IvyPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
+        this.tasks = tasks;
+        this.publishLifecycleTask = publishLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
+        publications.all(new Action<Publication>() {
+            public void execute(Publication publication) {
+                for (ArtifactRepository repository : repositories) {
+                    maybeCreate(publication, repository);
+                }
+            }
+        });
+
+        repositories.whenObjectAdded(new Action<ArtifactRepository>() {
+            public void execute(ArtifactRepository repository) {
+                for (Publication publication : publications) {
+                    maybeCreate(publication, repository);
+                }
+            }
+        });
+
+        // Note: we aren't supporting removal of repositories or publications
+        // Note: we also aren't considering that repos have a setName, so their name can change
+        //       (though this is a violation of the Named contract)
+    }
+
+    private void maybeCreate(Publication publication, ArtifactRepository repository) {
+        if (!(publication instanceof IvyPublicationInternal)) {
+            return;
+        }
+        if (!(repository instanceof IvyArtifactRepositoryInternal)) {
+            return;
+        }
+
+        IvyPublicationInternal publicationInternal = (IvyPublicationInternal) publication;
+        IvyArtifactRepositoryInternal repositoryInternal = (IvyArtifactRepositoryInternal) repository;
+
+        String publicationName = publication.getName();
+        String repositoryName = repository.getName();
+        String taskName = calculatePublishTaskName(publicationName, repositoryName);
+
+        PublishToIvyRepository task = tasks.add(taskName, PublishToIvyRepository.class);
+        task.setPublication(publicationInternal);
+        task.setRepository(repositoryInternal);
+        task.setGroup("publishing");
+        task.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'", publicationName, repositoryName));
+
+        publishLifecycleTask.dependsOn(task);
+    }
+
+    private String calculatePublishTaskName(String publicationName, String repositoryName) {
+        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/package-info.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/package-info.java
new file mode 100644
index 0000000..c48724f
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/package-info.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.
+ */
+
+/**
+ * Tasks for Ivy publishing.
+ *
+ * @since 1.3
+ */
+ at Incubating
+package org.gradle.api.publish.ivy.tasks;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/ivy/src/main/resources/META-INF/gradle-plugins/ivy-publish.properties b/subprojects/ivy/src/main/resources/META-INF/gradle-plugins/ivy-publish.properties
new file mode 100644
index 0000000..2740763
--- /dev/null
+++ b/subprojects/ivy/src/main/resources/META-INF/gradle-plugins/ivy-publish.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.publish.ivy.plugins.IvyPublishPlugin
\ No newline at end of file
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
new file mode 100644
index 0000000..a6ab5f8
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.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.api.publish.ivy.internal
+
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class DefaultIvyPublicationTest extends Specification {
+
+    ProjectInternal project = HelperUtil.createRootProject()
+
+    DefaultIvyPublication publication(String name = "ivy", Configuration... configurations) {
+        Instantiator instantiator = project.getServices().get(Instantiator)
+        instantiator.newInstance(DefaultIvyPublication, name, instantiator, configurations as Set, project, project.fileResolver, project.tasks)
+    }
+
+    def "publishable files are the artifact files"() {
+        when:
+        def file1 = project.file("file1")
+        project.configurations { conf1 }
+        project.artifacts { conf1 file1 }
+        def p = publication(project.configurations.conf1)
+
+        then:
+        p.publishableFiles.singleFile == file1
+
+        when:
+        def file2 = project.file("file2")
+        project.configurations { conf2 }
+        project.artifacts { conf2 file2 }
+        p = publication(project.configurations.conf1, project.configurations.conf2)
+
+        then:
+        p.publishableFiles.files == [file1, file2] as Set
+    }
+
+    def "publication is built by what builds the artifacts"() {
+        given:
+        project.plugins.apply(JavaBasePlugin)
+        Task dummyTask = project.task("dummyTask")
+
+        when:
+        def task1 = project.tasks.add("task1", Jar)
+        task1.baseName = "task1"
+        project.configurations { conf1 }
+        project.artifacts { conf1 task1 }
+        def p = publication(project.configurations.conf1)
+
+        then:
+        p.buildDependencies.getDependencies(dummyTask) == [task1] as Set
+
+        when:
+        def task2 = project.tasks.add("task2", Jar)
+        project.configurations { conf2 }
+        project.artifacts { conf2 task2 }
+        p = publication(project.configurations.conf1, project.configurations.conf2)
+
+        then:
+        p.buildDependencies.getDependencies(dummyTask) == [task1, task2] as Set
+    }
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
new file mode 100644
index 0000000..7f3662d
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
@@ -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.publish.ivy.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class IvyPublishPluginTest extends Specification {
+
+    Project project = HelperUtil.createRootProject()
+    PublishingExtension extension
+
+    def setup() {
+        project.plugins.apply(IvyPublishPlugin)
+        extension = project.extensions.getByType(PublishingExtension)
+    }
+
+    def "installs ivy publication"() {
+        expect:
+        extension.publications.size() == 1
+        extension.publications.toList().first() instanceof IvyPublication
+
+        IvyPublicationInternal publication = extension.publications.ivy
+        publication.name == "ivy"
+
+        when:
+        project.group = "foo"
+        project.version = 1.0
+        project.status = "integration"
+
+        and:
+        IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication()
+
+        then:
+        normalizedPublication.module.name == project.name
+        normalizedPublication.module.group == project.group
+        normalizedPublication.module.version == project.version.toString()
+        normalizedPublication.module.status == project.status
+
+        when:
+        project.group = "bar"
+        project.version = 2.0
+        project.status = "final"
+        normalizedPublication = publication.asNormalisedPublication()
+
+        then:
+        normalizedPublication.module.group == project.group
+        normalizedPublication.module.version == project.version.toString()
+        normalizedPublication.module.status == project.status
+    }
+
+    def "can configure descriptor"() {
+        given:
+        IvyPublicationInternal publication = extension.publications.ivy
+
+        when:
+        publication.descriptor {
+            withXml {
+                it.asNode(). at foo = "bar"
+            }
+        }
+
+        then:
+        publication.descriptor.transformer.transform("<things/>").contains('things foo="bar"')
+    }
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
new file mode 100644
index 0000000..70d0bc5
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.tasks
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository
+import org.gradle.api.internal.artifacts.repositories.IvyArtifactRepositoryInternal
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.api.publish.ivy.internal.IvyPublisher
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.tasks.TaskExecutionException
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class PublishToIvyRepositoryTest extends Specification {
+
+    Project project
+    PublishToIvyRepository publish
+
+    def normalizedPublication = Mock(IvyNormalizedPublication)
+
+    def publication = Mock(IvyPublicationInternal) {
+        asNormalisedPublication() >> normalizedPublication
+    }
+
+    def publisher = Mock(IvyPublisher) {}
+
+    def repository = Mock(IvyArtifactRepositoryInternal) {
+        createPublisher() >> publisher
+    }
+
+    def setup() {
+        project = HelperUtil.createRootProject()
+        publish = createPublish("publish")
+    }
+
+    def "publication must implement the internal interface"() {
+        when:
+        publish.publication = [:] as IvyPublication
+
+        then:
+        thrown(InvalidUserDataException)
+
+        when:
+        publish.publication = [:] as IvyPublicationInternal
+
+        then:
+        notThrown(Exception)
+    }
+
+    def "repository must implement the internal interface"() {
+        when:
+        publish.repository = [:] as IvyArtifactRepository
+
+        then:
+        thrown(InvalidUserDataException)
+
+        when:
+        publish.repository = [:] as IvyArtifactRepositoryInternal
+
+        then:
+        notThrown(Exception)
+    }
+
+    def "the dependencies of the publication are dependencies of the task"() {
+        given:
+        Task otherTask = project.task("other")
+        def publishableFiles = project.files("a", "b", "c")
+
+        publication.getPublishableFiles() >> publishableFiles
+        publication.getBuildDependencies() >> {
+            new TaskDependency() {
+                Set<? extends Task> getDependencies(Task task) {
+                    [otherTask] as Set
+                }
+            }
+        }
+
+        when:
+        publish.publication = publication
+
+        then:
+        publish.inputs.files.files == publishableFiles.files
+        publish.taskDependencies.getDependencies(publish) == [otherTask] as Set
+    }
+
+    def "repository and publication are required"() {
+        given:
+        repository = Mock(IvyArtifactRepositoryInternal) {
+            1 * createPublisher() >> publisher
+        }
+
+        when:
+        publish.execute()
+
+        then:
+        def e = thrown(TaskExecutionException)
+        e.cause instanceof InvalidUserDataException
+        e.cause.message == "The 'publication' property is required"
+
+        when:
+        publish = createPublish("publish2")
+        publish.publication = publication
+        publish.execute()
+
+        then:
+        e = thrown(TaskExecutionException)
+        e.cause instanceof InvalidUserDataException
+        e.cause.message == "The 'repository' property is required"
+
+        when:
+        publish = createPublish("publish3")
+        publish.publication = publication
+        publish.repository = repository
+        publish.execute()
+
+        then:
+        true
+    }
+
+    PublishToIvyRepository createPublish(String name) {
+        project.tasks.add(name, PublishToIvyRepository)
+    }
+
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy
new file mode 100644
index 0000000..ba3697a
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy
@@ -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.api.publish.ivy.tasks.internal
+
+import org.gradle.api.Task
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository
+import org.gradle.api.publish.plugins.PublishingPlugin
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class IvyPublishDynamicTaskCreatorTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+    def lifecycleTask = project.task("pl")
+    def creator = new IvyPublishDynamicTaskCreator(project.tasks, lifecycleTask)
+
+    PublishingExtension publishing
+
+    def setup() {
+        project.plugins.apply(PublishingPlugin)
+        publishing = project.extensions.getByType(PublishingExtension)
+        creator.monitor(publishing.publications, publishing.repositories)
+    }
+
+    def "creates tasks"() {
+        expect:
+        ivyPublishTasks.size() == 0
+
+        when:
+        publishing.repositories.ivy { }
+        publishing.publications.add(publication("foo"))
+
+        then:
+        ivyPublishTasks.size() == 0
+        lifecycleTaskDependencies.empty
+
+        when:
+        publishing.publications.add(ivyPublication("ivy"))
+
+        then:
+        ivyPublishTasks.size() == 1
+        project.tasks["publishIvyPublicationToIvyRepository"] != null
+        PublishToIvyRepository task = project.tasks.publishIvyPublicationToIvyRepository
+        task.group == "publishing"
+        task.description != null
+
+        lifecycleTaskDependencies == [task] as Set
+
+        when:
+        publishing.publications.add(ivyPublication("ivy2"))
+
+        then:
+        ivyPublishTasks.size() == 2
+        project.tasks["publishIvy2PublicationToIvyRepository"] != null
+        lifecycleTaskDependencies.size() == 2
+
+        when:
+        publishing.repositories.ivy {}
+
+        then:
+        lifecycleTaskDependencies.size() == 4
+        ivyPublishTasks.size() == 4
+        project.tasks["publishIvyPublicationToIvy2Repository"] != null
+        project.tasks["publishIvy2PublicationToIvy2Repository"] != null
+    }
+
+    protected Set<? extends Task> getLifecycleTaskDependencies() {
+        lifecycleTask.taskDependencies.getDependencies(lifecycleTask)
+    }
+
+    def getIvyPublishTasks() {
+        project.tasks.withType(PublishToIvyRepository)
+    }
+
+    Publication publication(String name) {
+        Mock(Publication) {
+            getName() >> name
+        }
+    }
+
+    IvyPublication ivyPublication(String name) {
+        Mock(IvyPublicationInternal) {
+            getName() >> name
+        }
+    }
+
+}
diff --git a/subprojects/javascript/javascript.gradle b/subprojects/javascript/javascript.gradle
index a0566bb..ce4823b 100644
--- a/subprojects/javascript/javascript.gradle
+++ b/subprojects/javascript/javascript.gradle
@@ -21,9 +21,10 @@ dependencies {
     compile "com.google.code.gson:gson:2.2.1" // used by JsHint
     compile "org.simpleframework:simple:4.1.21" // used by http package in envjs
     compile project(':core'), project(":plugins")
+    compile libraries.inject
 
     // Required by JavaScriptExtension#getGoogleApisRepository()
     compile project(':coreImpl')
 }
 
-useTestFixtures()
\ No newline at end of file
+useTestFixtures()
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
index d8375b8..3a3865a 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
@@ -19,17 +19,20 @@ package org.gradle.plugins.javascript.base
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.internal.artifacts.DependencyResolutionServices
-import org.gradle.api.internal.artifacts.ResolverFactory
-import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.BasePlugin
 
+import javax.inject.Inject
+
 class JavaScriptBasePlugin implements Plugin<Project> {
+    private final DependencyResolutionServices dependencyResolutionServices
 
-    void apply(Project project) {
-        ProjectInternal projectInternal = project as ProjectInternal
+    @Inject
+    JavaScriptBasePlugin(DependencyResolutionServices dependencyResolutionServices) {
+        this.dependencyResolutionServices = dependencyResolutionServices
+    }
 
+    void apply(Project project) {
         project.apply(plugin: BasePlugin)
-        ResolverFactory resolverFactory = projectInternal.services.get(DependencyResolutionServices).resolverFactory
-        JavaScriptExtension extension = project.extensions.create(JavaScriptExtension.NAME, JavaScriptExtension, resolverFactory)
+        project.extensions.create(JavaScriptExtension.NAME, JavaScriptExtension, dependencyResolutionServices.baseRepositoryFactory)
     }
 }
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
index dea76fd..6699de3 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
@@ -20,7 +20,7 @@ import groovy.lang.Closure;
 import org.gradle.api.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.artifacts.ResolverFactory;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
 import org.gradle.api.internal.artifacts.repositories.layout.PatternRepositoryLayout;
 
 public class JavaScriptExtension {
@@ -30,21 +30,21 @@ public class JavaScriptExtension {
     public static final String GRADLE_PUBLIC_JAVASCRIPT_REPO_URL = "http://repo.gradle.org/gradle/javascript-public";
     public static final String GOOGLE_APIS_REPO_URL = "http://ajax.googleapis.com/ajax/libs";
 
-    private final ResolverFactory resolverFactory;
+    private final BaseRepositoryFactory baseRepositoryFactory;
 
-    public JavaScriptExtension(ResolverFactory resolverFactory) {
-        this.resolverFactory = resolverFactory;
+    public JavaScriptExtension(BaseRepositoryFactory baseRepositoryFactory) {
+        this.baseRepositoryFactory = baseRepositoryFactory;
     }
 
     public ArtifactRepository getGradlePublicJavaScriptRepository() {
-        MavenArtifactRepository repo = resolverFactory.createMavenRepository();
+        MavenArtifactRepository repo = baseRepositoryFactory.createMavenRepository();
         repo.setUrl(GRADLE_PUBLIC_JAVASCRIPT_REPO_URL);
         repo.setName("Gradle Public JavaScript Repository");
         return repo;
     }
 
     public ArtifactRepository getGoogleApisRepository() {
-        IvyArtifactRepository repo = resolverFactory.createIvyRepository();
+        IvyArtifactRepository repo = baseRepositoryFactory.createIvyRepository();
         repo.setName("Google Libraries Repository");
         repo.setUrl(GOOGLE_APIS_REPO_URL);
         repo.layout("pattern", new Closure(this) {
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java
index 93cd900..839cd94 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java
@@ -19,7 +19,6 @@ package org.gradle.plugins.javascript.coffeescript;
 import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
@@ -32,6 +31,7 @@ import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
 import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
+import javax.inject.Inject;
 import java.io.File;
 
 public class CoffeeScriptCompile extends SourceTask {
@@ -40,6 +40,12 @@ public class CoffeeScriptCompile extends SourceTask {
     private Object destinationDir;
     private Object rhinoClasspath;
     private CoffeeScriptCompileOptions options = new CoffeeScriptCompileOptions();
+    private final Factory<WorkerProcessBuilder> workerProcessBuilderFactory;
+
+    @Inject
+    public CoffeeScriptCompile(Factory<WorkerProcessBuilder> workerProcessBuilderFactory) {
+        this.workerProcessBuilderFactory = workerProcessBuilderFactory;
+    }
 
     @InputFiles
     public FileCollection getCoffeeScriptJs() {
@@ -82,8 +88,6 @@ public class CoffeeScriptCompile extends SourceTask {
 
     @TaskAction
     public void doCompile() {
-        ProjectInternal projectInternal = (ProjectInternal)getProject();
-        Factory<WorkerProcessBuilder> workerProcessBuilderFactory = projectInternal.getServices().getFactory(WorkerProcessBuilder.class);
         RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
 
         CoffeeScriptCompileSpec spec = new DefaultCoffeeScriptCompileSpec();
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy
index d187395..70de3d9 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy
@@ -35,10 +35,17 @@ import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory
 import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory
 import org.gradle.process.internal.WorkerProcessBuilder
 
+import javax.inject.Inject
+
 import static org.gradle.plugins.javascript.envjs.EnvJsExtension.*
-import org.gradle.api.logging.LogLevel
 
 class EnvJsPlugin implements Plugin<Project> {
+    private final Factory<WorkerProcessBuilder> workerProcessBuilderFactory
+
+    @Inject
+    EnvJsPlugin(Factory<WorkerProcessBuilder> workerProcessBuilderFactory) {
+        this.workerProcessBuilderFactory = workerProcessBuilderFactory
+    }
 
     void apply(Project project) {
         project.plugins.apply(RhinoPlugin)
@@ -59,10 +66,8 @@ class EnvJsPlugin implements Plugin<Project> {
         project.tasks.withType(BrowserEvaluate) { BrowserEvaluate task ->
             conventionMapping.with {
                 map("evaluator") {
-                    Factory<WorkerProcessBuilder> workerProcessBuilderFactory = project.services.getFactory(WorkerProcessBuilder);
                     RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
 
-                    LogLevel logLevel = task.logging.level
                     File workDir = project.projectDir
                     Factory<File> envJsFactory = new Factory<File>() {
                         File create() {
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java
index 091c9e4..6cec11d 100644
--- a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java
@@ -21,7 +21,6 @@ import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.tasks.*;
@@ -36,6 +35,7 @@ import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHan
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -49,6 +49,12 @@ public class JsHint extends SourceTask {
     private Object jsHint;
     private String encoding = "UTF-8";
     private Object jsonReport;
+    private final Factory<WorkerProcessBuilder> workerProcessBuilderFactory;
+
+    @Inject
+    public JsHint(Factory<WorkerProcessBuilder> workerProcessBuilderFactory) {
+        this.workerProcessBuilderFactory = workerProcessBuilderFactory;
+    }
 
     @InputFiles
     public FileCollection getRhinoClasspath() {
@@ -88,8 +94,6 @@ public class JsHint extends SourceTask {
 
     @TaskAction
     public void doJsHint() {
-        ProjectInternal projectInternal = (ProjectInternal)getProject();
-        Factory<WorkerProcessBuilder> workerProcessBuilderFactory = projectInternal.getServices().getFactory(WorkerProcessBuilder.class);
         RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
 
         LogLevel logLevel = getProject().getGradle().getStartParameter().getLogLevel();
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
index e2ba0ff..b86f718 100644
--- a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
@@ -22,36 +22,23 @@ import spock.lang.Specification
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 
 class JavaScriptBasePluginTest extends Specification {
-
     Project project = ProjectBuilder.builder().build()
-    JavaScriptExtension extension
-
-    def setup() {
-        apply(plugin: JavaScriptBasePlugin)
-        extension = javaScript
-    }
-
-    def methodMissing(String name, args) {
-        project."$name"(*args)
-    }
-
-    def propertyMissing(String name) {
-        project."$name"
-    }
-
-    def propertyMissing(String name, value) {
-        project."$name" = value
-    }
 
     def "extension is available"() {
-        expect:
-        extension != null
+        when:
+        project.apply(plugin: JavaScriptBasePlugin)
+
+        then:
+        project.javaScript != null
     }
 
     def "can get public repo"() {
-        expect:
-        extension.gradlePublicJavaScriptRepository instanceof MavenArtifactRepository
-        MavenArtifactRepository repo = extension.gradlePublicJavaScriptRepository as MavenArtifactRepository
+        when:
+        project.apply(plugin: JavaScriptBasePlugin)
+
+        then:
+        project.javaScript.gradlePublicJavaScriptRepository instanceof MavenArtifactRepository
+        MavenArtifactRepository repo = project.javaScript.gradlePublicJavaScriptRepository as MavenArtifactRepository
         repo.url.toString() == JavaScriptExtension.GRADLE_PUBLIC_JAVASCRIPT_REPO_URL
     }
 
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index 1141118..4c9ae16 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -1,5 +1,3 @@
-apply from: "$rootDir/gradle/classycle.gradle"
-
 configurations {
     startScriptGenerator
 }
@@ -17,6 +15,8 @@ dependencies {
     compile libraries.slf4j_api
 
     startScriptGenerator project(':plugins')
+
+    testRuntime project(':diagnostics')
 }
 
 useTestFixtures()
@@ -81,3 +81,5 @@ daemonIntegTest {
     //excluding to avoid unnecessary re-running and stealing resources.
     exclude "org/gradle/launcher/daemon/**/*"
 }
+
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.groovy
new file mode 100644
index 0000000..c996cb4
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/GradleConfigurabilityIntegrationSpec.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.launcher
+
+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.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.TextUtil
+import spock.lang.IgnoreIf
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+ at IgnoreIf( { !GradleDistributionExecuter.getSystemPropertyExecuter().forks })
+class GradleConfigurabilityIntegrationSpec extends AbstractIntegrationSpec {
+
+    def setup() {
+        distribution.requireIsolatedDaemons()
+    }
+
+    def buildSucceeds(String script) {
+        distribution.file('build.gradle') << script
+        executer.withArguments("--info").withNoDefaultJvmArgs().run()
+    }
+
+    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')
+        """
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "connects to the daemon if java home is a symlink"() {
+        given:
+        def javaHome = Jvm.current().javaHome
+        def javaLink = distribution.testFile("javaLink")
+        FileSystems.default.createSymbolicLink(javaLink, javaHome)
+        distribution.getTestDir().file("tmp").deleteDir().createDir()
+
+        String linkPath = TextUtil.escapeString(javaLink.absolutePath)
+        distribution.file("gradle.properties") << "org.gradle.java.home=$linkPath"
+
+        when:
+        buildSucceeds "println 'java home =' + System.getProperty('java.home')"
+
+        then:
+        javaLink != javaHome
+        javaLink.canonicalFile == javaHome.canonicalFile
+
+        cleanup:
+        javaLink.usingNativeTools().deleteDir()
+    }
+
+    //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/DaemonConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
deleted file mode 100644
index 001bce2..0000000
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.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.launcher.daemon
-
-import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.internal.jvm.Jvm
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-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')
-        """
-    }
-
-    @Requires(TestPrecondition.SYMLINKS)
-    def "connects to the daemon if java home is a symlink"() {
-        given:
-        def javaHome = Jvm.current().javaHome
-        def javaLink = distribution.testFile("javaLink")
-        FileSystems.default.createSymbolicLink(javaLink, javaHome)
-
-        String linkPath = TextUtil.escapeString(javaLink.absolutePath)
-        distribution.file("gradle.properties") << "org.gradle.java.home=$linkPath"
-
-        when:
-        buildSucceeds "println 'Hello!'"
-
-        then:
-        javaLink != javaHome
-        javaLink.canonicalFile == javaHome.canonicalFile
-
-        cleanup:
-        javaLink.usingNativeTools().deleteDir()
-    }
-
-    //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
index 262167a..fbcd828 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
@@ -26,15 +26,12 @@ import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
  * by Szczepan Faber, created at: 1/20/12
  */
 class DaemonFeedbackIntegrationSpec extends DaemonIntegrationSpec {
-
-    def cleanup() {
-        stopDaemonsNow()
+    def setup() {
+        distribution.requireIsolatedDaemons()
     }
 
     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...'
@@ -48,16 +45,16 @@ task sleep << {
 
         then:
         poll(60) {
-            assert readLog(baseDir).contains("taking a nap...")
+            assert readLog(distribution.daemonBaseDir).contains("taking a nap...")
         }
 
         when:
-        executer.withDaemonBaseDir(baseDir).withArguments("--stop").run()
+        executer.withArguments("--stop").run()
 
         then:
         sleeper.waitForFailure()
 
-        def log = readLog(baseDir)
+        def log = readLog(distribution.daemonBaseDir)
         assert log.contains(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP)
         assert log.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)
     }
@@ -90,15 +87,13 @@ task sleep << {
 
     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)
+        def log = readLog(distribution.daemonBaseDir)
 
         //output before started relying logs via connection
         log.count(DaemonMessages.PROCESS_STARTED) == 1
@@ -111,7 +106,7 @@ task sleep << {
         executer.withArguments("-i").run()
 
         then:
-        def aLog = readLog(baseDir)
+        def aLog = readLog(distribution.daemonBaseDir)
 
         aLog.count(DaemonMessages.PROCESS_STARTED) == 1
         aLog.count(DaemonMessages.STARTED_RELAYING_LOGS) == 2
@@ -120,21 +115,19 @@ task sleep << {
 
     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)
+        def log = readLog(distribution.daemonBaseDir)
         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)
+            def daemonLog = readLog(distribution.daemonBaseDir)
             daemonLog.findAll(DaemonMessages.FINISHED_EXECUTING_COMMAND).size() == 1
             daemonLog.findAll(DaemonMessages.FINISHED_BUILD).size() == 1
         }
@@ -143,14 +136,12 @@ task sleep << {
         executer.withArguments("-i").withTasks('foo').run()
 
         then:
-        def aLog = readLog(baseDir)
+        def aLog = readLog(distribution.daemonBaseDir)
         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!'
 
@@ -166,7 +157,7 @@ task sleep << {
         executer.withArguments("-q").run()
 
         then:
-        def log = readLog(baseDir)
+        def log = readLog(distribution.daemonBaseDir)
 
         //daemon logs to file eagerly regardless of the build log level
         log.count(DaemonMessages.STARTED_RELAYING_LOGS) == 1
@@ -182,8 +173,6 @@ task sleep << {
 
     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:
@@ -196,23 +185,22 @@ task sleep << {
 
     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()
+        def daemon = executer.setAllowExtraLogging(false).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()
+        def infoBuild = executer.withArguments("-i", "-Dorg.gradle.jvmargs=-ea").run()
 
         then:
-        getLogs(baseDir).size() == 0 //we should connect to the foreground daemon so no log was created
+        getLogs(distribution.daemonBaseDir).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
@@ -221,7 +209,7 @@ task sleep << {
         infoBuild.output.count("info me!") == 1
 
         when:
-        def debugBuild = executer.withDaemonBaseDir(baseDir).withArguments("-d", "-Dorg.gradle.jvmargs=-ea").run()
+        def debugBuild = executer.withArguments("-d", "-Dorg.gradle.jvmargs=-ea").run()
 
         then:
         daemon.standardOutput.count(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS) == 0
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
new file mode 100644
index 0000000..f017955
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonInitialCommunicationFailureIntegrationSpec.groovy
@@ -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.
+ */
+
+package org.gradle.launcher.daemon
+
+import org.gradle.integtests.fixtures.KillProcessAvailability
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.launcher.daemon.testing.DaemonLogsAnalyzer
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+import spock.lang.IgnoreIf
+import spock.lang.Issue
+
+import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+ at IgnoreIf({ !KillProcessAvailability.CAN_KILL })
+class DaemonInitialCommunicationFailureIntegrationSpec extends DaemonIntegrationSpec {
+
+    @Rule HttpServer server = new HttpServer()
+
+    def cleanup() {
+        stopDaemonsNow()
+    }
+
+    @Issue("GRADLE-2444")
+    def "behaves if the registry contains connectable port without daemon on the other end"() {
+        when:
+        buildSucceeds()
+
+        then:
+        //there should be one idle daemon
+        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+
+        when:
+        // Wait until the daemon has finished updating the registry. Killing it halfway through the registry update will leave the registry corrupted,
+        // and the client will just throw the registry away and replace it with an empty one
+        daemon.waitUntilIdle()
+        daemon.kill()
+
+        and:
+        //starting some service on the daemon port
+        poll {
+            server.start(daemon.port)
+        }
+
+        then:
+        //most fundamentally, the build works ok:
+        buildSucceeds()
+
+        and:
+        output.contains DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE
+
+        when:
+        buildSucceeds()
+
+        then:
+        //suspected address was removed from the registry
+        // so we should the client does not attempt to connect to it again
+        !output.contains(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE)
+    }
+
+    @Issue("GRADLE-2444")
+    def "stop() behaves if the registry contains connectable port without daemon on the other end"() {
+        when:
+        buildSucceeds()
+
+        then:
+        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+
+        when:
+        daemon.waitUntilIdle()
+        daemon.kill()
+        poll {
+            server.start(daemon.port)
+        }
+
+        then:
+        //most fundamentally, stopping works ok:
+        stopDaemonsNow()
+
+        and:
+        output.contains DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE
+
+        when:
+        stopDaemonsNow()
+
+        then:
+        !output.contains(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE)
+        output.contains(DaemonMessages.NO_DAEMONS_RUNNING)
+    }
+
+    @Issue("GRADLE-2464")
+    def "when nothing responds to the address found in the registry we remove the address"() {
+        when:
+        buildSucceeds()
+
+        then:
+        def daemon = new DaemonLogsAnalyzer(distribution.daemonBaseDir).daemon
+
+        when:
+        daemon.waitUntilIdle()
+        daemon.kill()
+
+        then:
+        buildSucceeds()
+
+        and:
+        def analyzer = new DaemonLogsAnalyzer(distribution.daemonBaseDir)
+        analyzer.daemons.size() == 2        //2 daemon participated
+        analyzer.registry.all.size() == 1   //only one address in the registry
+    }
+}
\ No newline at end of file
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
index 3518bc0..a23b5d5 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
@@ -22,6 +22,7 @@ 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
 
 /**
@@ -31,6 +32,7 @@ class DaemonIntegrationSpec extends Specification {
 
     @Rule public final GradleDistribution distribution = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter(daemon)
+    String output
 
     def setup() {
         distribution.requireIsolatedDaemons()
@@ -38,11 +40,13 @@ class DaemonIntegrationSpec extends Specification {
     }
 
     void stopDaemonsNow() {
-        executer.withArguments("--stop", "--info").run()
+        def result = executer.withArguments("--stop", "--info").run()
+        output = result.output
     }
 
-    void buildSucceeds(String script) {
+    void buildSucceeds(String script = '') {
         distribution.file('build.gradle') << script
-        executer.withArguments("--info", "-Dorg.gradle.jvmargs=").run()
+        def result = executer.withArguments("--info").withNoDefaultJvmArgs().run()
+        output = result.output
     }
 }
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
index 6bb4369..7a67885 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
@@ -18,12 +18,13 @@ package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
 import org.gradle.integtests.fixtures.GradleHandle
+import org.gradle.internal.jvm.Jvm
 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
 
 /**
@@ -53,14 +54,6 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
     @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")
     }
@@ -102,7 +95,7 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
             """)
             builds << executer.start()
         }
-        //TODO SF - figure out how to add waitForBuildToWait somewhere here
+        //TODO SF - rewrite the lifecycle spec so that it uses the TestableDaemon
     }
 
     void completeBuild(buildNum = 0) {
@@ -113,7 +106,7 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
 
     void waitForBuildToWait(buildNum = 0) {
         run {
-            poll { assert builds[buildNum].standardOutput.contains("waiting for stop file"); }
+            poll(20) { assert builds[buildNum].standardOutput.contains("waiting for stop file"); }
         }
     }
 
@@ -407,10 +400,8 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
         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; }
+        // The daemon attempts to remove its address on shutdown
+        run { assert executer.daemonRegistry.all.empty }
     }
 
     @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.groovy
new file mode 100644
index 0000000..b5c4ade
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonSystemPropertiesIntegrationTest.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.launcher.daemon
+
+import spock.lang.Issue
+
+ at Issue("GRADLE-2460")
+class DaemonSystemPropertiesIntegrationTest extends DaemonIntegrationSpec {
+    def "standard and sun. client JVM system properties are not carried over to daemon JVM"() {
+        given:
+        distribution.file("build.gradle") << """
+task verify << {
+    assert System.getProperty("java.vendor") != "hollywood"
+    assert System.getProperty("java.vendor") != null
+    assert System.getProperty("sun.sunny") == null
+}
+        """
+
+        expect:
+        executer.withEnvironmentVars(GRADLE_OPTS: "-Djava.vendor=hollywood -Dsun.sunny=california").withTasks("verify").run()
+    }
+
+    def "other client JVM system properties are carried over to daemon JVM"() {
+        given:
+        distribution.file("build.gradle") << """
+task verify << {
+    assert System.getProperty("foo.bar") == "baz"
+}
+        """
+
+        expect:
+        executer.withEnvironmentVars(GRADLE_OPTS: "-Dfoo.bar=baz").withTasks("verify").run()
+
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.groovy
new file mode 100644
index 0000000..f665159
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonIntegrationSpec.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.launcher.daemon
+
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.util.TextUtil
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+class StoppingDaemonIntegrationSpec extends DaemonIntegrationSpec {
+    def "can handle multiple concurrent stop requests"() {
+        given:
+        def projectDir = distribution.testDir
+        projectDir.file('build.gradle') << '''
+file('marker.txt') << 'waiting'
+Thread.sleep(60000)
+'''
+
+        when:
+        def build = executer.start()
+        ConcurrentTestUtil.poll(20) { assert projectDir.file('marker.txt').file }
+
+        def stopExecutions = []
+        5.times { idx ->
+            stopExecutions << executer.withArguments("--stop").start()
+        }
+        stopExecutions.each { it.waitForFinish() }
+        build.waitForFailure()
+        def out = executer.withArguments("--stop").run().output
+
+        then:
+        out.contains(DaemonMessages.NO_DAEMONS_RUNNING)
+    }
+
+    def "reports exact number of daemons stopped and keeps console output clean"() {
+        given:
+        executer.allowExtraLogging = false
+        executer.run()
+
+        when:
+        def out = executer.withArguments("--stop").run().output
+
+        then:
+        out == TextUtil.toPlatformLineSeparators('''Stopping daemon(s).
+Gradle daemon stopped.
+''')
+
+        when:
+        out = executer.withArguments("--stop").run().output
+
+        then:
+        out == TextUtil.toPlatformLineSeparators("""$DaemonMessages.NO_DAEMONS_RUNNING
+""")
+    }
+}
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
deleted file mode 100644
index ffde577..0000000
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
+++ /dev/null
@@ -1,89 +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.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.Ignore
-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)
-    @Ignore
-    //TODO SF - un-ignore when the problem with daemon not shown in the registry file is fixed.
-    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")
-        printDaemonLogs()
-    }
-
-    void printDaemonLogs() {
-        temp.testDir.listFiles()[0].listFiles().findAll { it.name.endsWith("log") }.each {
-            println "-------- $it.name ----------"
-            println it.text
-            println "----------------------------"
-        }
-    }
-
-    //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
index c4ed7a0..8f44b57 100644
--- 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
@@ -39,7 +39,8 @@ public class DaemonContextParser {
             String uid = matcher.group(1);
             String javaHome = matcher.group(2);
             String daemonRegistryDir = matcher.group(3);
-            Long pid = Long.parseLong(matcher.group(4));
+            String pidStr = matcher.group(4);
+            Long pid = pidStr.equals("null") ? null : Long.parseLong(pidStr);
             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);
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
index 97baba3..c2ed8e3 100644
--- 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
@@ -19,7 +19,7 @@ import org.gradle.launcher.daemon.registry.DaemonRegistry
 
 class DaemonEventSequenceBuilder {
 
-    int pollRegistryMs = 300
+    int pollRegistryMs = 50
     final int stateTransitionTimeoutMs
 
     DaemonsState currentState = null
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.groovy
new file mode 100644
index 0000000..00cb479
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonLogsAnalyzer.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.launcher.daemon.testing
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices
+
+/**
+ * by Szczepan Faber, created at: 9/3/12
+ */
+class DaemonLogsAnalyzer {
+
+    private List<File> daemonLogs
+    private File daemonBaseDir
+    private DaemonRegistryServices services
+
+    DaemonLogsAnalyzer(File daemonBaseDir) {
+        this.daemonBaseDir = daemonBaseDir
+        assert daemonBaseDir.listFiles().length == 1
+        def daemonFiles = daemonBaseDir.listFiles()[0].listFiles()
+        daemonLogs = daemonFiles.findAll { it.name.endsWith('.log') }
+        services = new DaemonRegistryServices(daemonBaseDir)
+    }
+
+    List<TestableDaemon> getDaemons() {
+        return daemonLogs.collect { new TestableDaemon(it, registry) }
+    }
+
+    TestableDaemon getDaemon() {
+        def daemons = getDaemons()
+        assert daemons.size() == 1: "Expected only a single daemon."
+        daemons[0]
+    }
+
+    DaemonRegistry getRegistry() {
+        services.get(DaemonRegistry)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.groovy
new file mode 100644
index 0000000..0eb3065
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/TestableDaemon.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.launcher.daemon.testing
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.process.internal.ExecHandleBuilder
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class TestableDaemon {
+
+    final DaemonContext context
+    final String logContent
+    private final DaemonRegistry registry
+
+    TestableDaemon(File daemonLog, DaemonRegistry registry) {
+        this.logContent = daemonLog.text
+        this.context = DaemonContextParser.parseFrom(logContent)
+        this.registry = registry
+    }
+
+    void waitUntilIdle() {
+        def expiry = System.currentTimeMillis() + 20000
+        while (expiry > System.currentTimeMillis()) {
+            if (registry.idle.find { it.context.pid == context.pid } != null) {
+                return
+            }
+            Thread.sleep(200)
+        }
+        throw new AssertionError("Timeout waiting for daemon with pid ${context.pid} to become idle.")
+    }
+
+    void kill() {
+        println "Killing daemon with pid: $context.pid"
+        def output = new ByteArrayOutputStream()
+        def e = new ExecHandleBuilder()
+                .commandLine(killArgs(context.pid))
+                .redirectErrorStream()
+                .setStandardOutput(output)
+                .workingDir(new File(".").absoluteFile) //does not matter
+                .build()
+        e.start()
+        def result = e.waitForFinish()
+        result.rethrowFailure()
+    }
+
+    private static Object[] killArgs(Long pid) {
+        if (pid == null) {
+            throw new RuntimeException("Unable to force kill the daemon because provided pid is null!")
+        }
+        if (OperatingSystem.current().isUnix()) {
+            return ["kill", "-9", pid]
+        } else if (OperatingSystem.current().isWindows()) {
+            return ["taskkill.exe", "/F", "/T", "/PID", pid]
+        }
+        else {
+            throw new RuntimeException("This implementation does not know how to forcefully kill the daemon on os: " + OperatingSystem.current())
+        }
+    }
+
+    enum State {
+        busy, idle
+    }
+
+    boolean isIdle() {
+        getStates()[-1] == State.idle
+    }
+
+    boolean isBusy() {
+        !isIdle()
+    }
+
+    List<State> getStates() {
+        def states = new LinkedList<State>()
+        logContent.eachLine {
+            if (it.contains(DaemonMessages.DAEMON_IDLE)) {
+                states << State.idle
+            } else if (it.contains(DaemonMessages.DAEMON_BUSY)) {
+                states << State.busy
+            }
+        }
+        states
+    }
+
+    int getPort() {
+        Pattern pattern = Pattern.compile("^.*" + DaemonMessages.ADVERTISING_DAEMON + ".*port:(\\d+).*",
+                Pattern.MULTILINE + Pattern.DOTALL);
+
+        Matcher matcher = pattern.matcher(logContent);
+        assert matcher.matches(): "Unable to find daemon address in the daemon log. Daemon: $context"
+
+        try {
+            return Integer.parseInt(matcher.group(1))
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("Unexpected format of the port number found in the daemon log. Daemon: $context")
+        }
+    }
+}
\ No newline at end of file
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
deleted file mode 100644
index c195a99..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.bootstrap.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
index 7c3a7a4..4656fab 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
@@ -18,6 +18,7 @@ package org.gradle.launcher.cli;
 
 import org.gradle.StartParameter;
 import org.gradle.api.Action;
+import org.gradle.api.internal.Actions;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
@@ -25,6 +26,7 @@ import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.launcher.daemon.bootstrap.ForegroundDaemonMain;
 import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.client.DaemonClientServices;
@@ -34,7 +36,6 @@ 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.BuildActionParameters;
-import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
 
@@ -63,13 +64,13 @@ class BuildActionsFactory implements CommandLineAction {
     public void configureCommandLineParser(CommandLineParser parser) {
         startParameterConverter.configure(parser);
 
-        parser.option(FOREGROUND).hasDescription("Starts the Gradle daemon in the foreground.").experimental();
+        parser.option(FOREGROUND).hasDescription("Starts the Gradle daemon in the foreground.").incubating();
         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) {
+    public Action<? super ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
         StartParameter startParameter = new StartParameter();
         startParameterConverter.convert(commandLine, startParameter);
         DaemonParameters daemonParameters = constructDaemonParameters(startParameter);
@@ -79,7 +80,7 @@ class BuildActionsFactory implements CommandLineAction {
         if (commandLine.hasOption(FOREGROUND)) {
             ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration(
                     daemonParameters.getUid(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout());
-            return new ActionAdapter(new ForegroundDaemonMain(conf));
+            return Actions.toAction(new ForegroundDaemonMain(conf));
         }
         if (useDaemon(commandLine, daemonParameters)) {
             return runBuildWithDaemon(startParameter, daemonParameters, loggingServices);
@@ -99,10 +100,10 @@ class BuildActionsFactory implements CommandLineAction {
         return daemonParameters;
     }
 
-    private Action<ExecutionListener> stopAllDaemons(DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+    private Action<? super 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));
+        return Actions.toAction(new StopDaemonAction(stopClient));
     }
 
     private boolean useDaemon(ParsedCommandLine commandLine, DaemonParameters daemonParameters) {
@@ -112,7 +113,7 @@ class BuildActionsFactory implements CommandLineAction {
         return useDaemon;
     }
 
-    private Action<ExecutionListener> runBuildWithDaemon(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+    private Action<? super 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);
@@ -124,12 +125,12 @@ class BuildActionsFactory implements CommandLineAction {
         return currentProcess.configureForBuild(requiredBuildParameters);
     }
 
-    private Action<ExecutionListener> runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+    private Action<? super ExecutionListener> runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
         InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(new DefaultGradleLauncherFactory(loggingServices));
         return daemonBuildAction(startParameter, daemonParameters, executer);
     }
 
-    private Action<ExecutionListener> runBuildInSingleUseDaemon(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+    private Action<? super 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.
@@ -145,8 +146,8 @@ class BuildActionsFactory implements CommandLineAction {
         return daemonBuildAction(startParameter, daemonParameters, client);
     }
 
-    private Action<ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, GradleLauncherActionExecuter<BuildActionParameters> executer) {
-        return new ActionAdapter(
+    private Action<? super ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, GradleLauncherActionExecuter<BuildActionParameters> executer) {
+        return Actions.toAction(
                 new RunBuildAction(executer, startParameter, getWorkingDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
     }
 
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
index aa77808..82e6082 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
@@ -31,5 +31,5 @@ public interface CommandLineAction {
      * 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);
+    Action<? super 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
index b0d37df..53ffb1a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
@@ -17,6 +17,7 @@ package org.gradle.launcher.cli;
 
 import org.gradle.BuildExceptionReporter;
 import org.gradle.api.Action;
+import org.gradle.api.internal.Actions;
 import org.gradle.cli.CommandLineArgumentException;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.cli.CommandLineParser;
@@ -89,12 +90,12 @@ public class CommandLineActionFactory {
             parser.option(VERSION, "version").hasDescription("Print version info.");
         }
 
-        public Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+        public Action<? super ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
             if (commandLine.hasOption(HELP)) {
-                return new ActionAdapter(new ShowUsageAction(parser));
+                return Actions.toAction(new ShowUsageAction(parser));
             }
             if (commandLine.hasOption(VERSION)) {
-                return new ActionAdapter(new ShowVersionAction());
+                return Actions.toAction(new ShowVersionAction());
             }
             return null;
         }
@@ -163,8 +164,8 @@ public class CommandLineActionFactory {
 
             LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
             loggingManager.setLevel(loggingConfiguration.getLogLevel());
-            loggingManager.colorStdOutAndStdErr(loggingConfiguration.isColorOutput());
             loggingManager.start();
+            loggingManager.attachConsole(loggingConfiguration.isColorOutput());
 
             action.execute(executionListener);
         }
@@ -189,7 +190,7 @@ public class CommandLineActionFactory {
                 action.configureCommandLineParser(parser);
             }
 
-            Action<ExecutionListener> action;
+            Action<? super ExecutionListener> action;
             try {
                 ParsedCommandLine commandLine = parser.parse(args);
                 action = createAction(actions, parser, commandLine);
@@ -200,9 +201,9 @@ public class CommandLineActionFactory {
             action.execute(executionListener);
         }
 
-        private Action<ExecutionListener> createAction(Iterable<CommandLineAction> factories, CommandLineParser parser, ParsedCommandLine commandLine) {
+        private Action<? super ExecutionListener> createAction(Iterable<CommandLineAction> factories, CommandLineParser parser, ParsedCommandLine commandLine) {
             for (CommandLineAction factory : factories) {
-                Action<ExecutionListener> action = factory.createAction(parser, commandLine);
+                Action<? super ExecutionListener> action = factory.createAction(parser, commandLine);
                 if (action != null) {
                     return action;
                 }
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
index 098bd3a..fc3f9b0 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
@@ -17,6 +17,7 @@
 package org.gradle.launcher.cli;
 
 import org.gradle.api.Action;
+import org.gradle.api.internal.Actions;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
@@ -29,9 +30,9 @@ class GuiActionsFactory implements CommandLineAction {
         parser.option(GUI).hasDescription("Launches the Gradle GUI.");
     }
 
-    public Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+    public Action<? super ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
         if (commandLine.hasOption(GUI)) {
-            return new ActionAdapter(new ShowGuiAction());
+            return Actions.toAction(new ShowGuiAction());
         }
         return null;
     }
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
index 575089e..54aa641 100644
--- 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
@@ -27,14 +27,16 @@ 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.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.logging.internal.OutputEventRenderer;
 
-import java.io.*;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * The entry point for a daemon process.
@@ -91,13 +93,13 @@ public class DaemonMain extends EntryPoint {
     }
 
     protected void doAction(ExecutionListener listener) {
-        LoggingServiceRegistry loggingRegistry = LoggingServiceRegistry.newChildProcessLogging();
-        LoggingManagerInternal loggingManager = loggingRegistry.getFactory(LoggingManagerInternal.class).create();
+        LoggingServiceRegistry loggingRegistry = LoggingServiceRegistry.newProcessLogging();
+        LoggingManagerInternal loggingManager = loggingRegistry.newInstance(LoggingManagerInternal.class);
         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);
+        initialiseLogging(loggingManager, daemonLog);
 
         Runtime.getRuntime().addShutdownHook(new Thread() {
             public void run() {
@@ -112,26 +114,29 @@ public class DaemonMain extends EntryPoint {
         daemonStarted(pid, daemonLog);
 
         try {
-            daemon.awaitIdleTimeout(configuration.getIdleTimeout());
+            daemon.requestStopOnIdleTimeout(configuration.getIdleTimeout(), TimeUnit.MILLISECONDS);
             LOGGER.info("Daemon hit idle timeout (" + configuration.getIdleTimeout() + "ms), stopping...");
+        } finally {
             daemon.stop();
-        } catch (DaemonStoppedException e) {
-            LOGGER.debug("Daemon stopping due to the stop request");
-            listener.onFailure(e);
         }
     }
 
     protected void daemonStarted(Long pid, File daemonLog) {
         //directly printing to the stream to avoid log level filtering.
         new DaemonStartupCommunication().printDaemonStarted(originalOut, pid, daemonLog);
-        originalOut.close();
-        originalErr.close();
-
-        //TODO - make this work on windows
-        //originalIn.close();
+        try {
+            originalOut.close();
+            originalErr.close();
+
+            //TODO - make this work on windows
+            //originalIn.close();
+        } finally {
+            originalOut = null;
+            originalErr = null;
+        }
     }
 
-    protected void initialiseLogging(OutputEventRenderer renderer, LoggingManagerInternal loggingManager, File daemonLog) {
+    protected void initialiseLogging(LoggingManagerInternal loggingManager, File daemonLog) {
         //create log file
         PrintStream result;
         try {
@@ -155,7 +160,7 @@ public class DaemonMain extends EntryPoint {
 
         //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();
+        loggingManager.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)
@@ -170,13 +175,11 @@ public class DaemonMain extends EntryPoint {
         return daemon;
     }
 
-    private void redirectOutputsAndInput(OutputStream log) {
+    private void redirectOutputsAndInput(PrintStream printStream) {
         this.originalOut = System.out;
         this.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]));
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
index 7725d8d..2ea6f52 100644
--- 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
@@ -20,7 +20,6 @@ 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;
 
@@ -31,7 +30,7 @@ public class ForegroundDaemonMain extends DaemonMain {
     }
 
     @Override
-    protected void initialiseLogging(OutputEventRenderer renderer, LoggingManagerInternal loggingManager, File daemonLog) {
+    protected void initialiseLogging(LoggingManagerInternal loggingManager, File daemonLog) {
         // Don't redirect IO for foreground daemon
         loggingManager.start();
     }
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
index 7f65171..ad0bf4d 100644
--- 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.api.GradleException;
 import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -31,31 +32,46 @@ 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;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * 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>To execute a build:</p>
+ *
+ * <ul>
+ * <li>The client creates a connection to daemon.</li>
+ * <li>The client sends exactly one {@link Build} message.</li>
+ * <li>The daemon sends exactly one {@link BuildStarted}, {@link Failure} or {@link DaemonUnavailable} message.</li>
+ * <li>If the build is started, the daemon may send zero or more {@link OutputEvent} messages.</li>
+ * <li>If the build is started, the client may send zero or more {@link ForwardInput} messages followed by exactly one {@link CloseInput} message.</li>
+ * <li>The daemon sends exactly one {@link Result} message. It may no longer send any messages.</li>
+ * <li>The client sends a {@link CloseInput} message, if not already sent. It may no longer send any {@link ForwardInput} messages.</li>
+ * <li>The client sends a {@link Finished} message. It may no longer messages.</li>
+ * <li>The client closes the connection.</li>
+ * <li>The daemon closes the connection.</li>
+ * </ul>
+ *
+ * <p>To stop a daemon:</p>
+ *
+ * <ul>
+ * <li>The client creates a connection to daemon.</li>
+ * <li>The client sends exactly one {@link Stop} message.</li>
+ * <li>The daemon sends exactly one {@link Result} message. It may no longer send any messages.</li>
+ * <li>The client sends a {@link Finished} message. It may no longer messages.</li>
+ * <li>The client closes the connection.</li>
+ * <li>The daemon closes the connection.</li>
+ * </ul>
+ *
  * <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);
+    private static final int STOP_TIMEOUT_SECONDS = 30;
     private final DaemonConnector connector;
     private final OutputEventListener outputEventListener;
     private final ExplainingSpec<DaemonContext> compatibilitySpec;
@@ -87,19 +103,35 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
      * Stops all daemons, if any is running.
      */
     public void stop() {
-        DaemonConnection connection = connector.maybeConnect(compatibilitySpec);
+        long start = System.currentTimeMillis();
+        long expiry = start + STOP_TIMEOUT_SECONDS * 1000;
+        Set<String> stopped = new HashSet<String>();
+
+        // TODO - only connect to daemons that we have not yet sent a stop request to
+        DaemonClientConnection 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(idGenerator).dispatch(connection.getConnection());
-            LOGGER.lifecycle("Gradle daemon stopped.");
+        while (connection != null && System.currentTimeMillis() < expiry) {
+            try {
+                if (stopped.add(connection.getUid())) {
+                    new StopDispatcher(idGenerator).dispatch(connection);
+                    LOGGER.lifecycle("Gradle daemon stopped.");
+                }
+            } finally {
+                connection.stop();
+            }
             connection = connector.maybeConnect(compatibilitySpec);
         }
+
+        if (connection != null) {
+            throw new GradleException(String.format("Timeout waiting for all daemons to stop. Waited %s seconds.", (System.currentTimeMillis() - start) / 1000));
+        }
     }
 
     /**
@@ -112,45 +144,57 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
         Build build = new Build(idGenerator.generateId(), 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();
-
+            DaemonClientConnection connection = connector.connect(compatibilitySpec);
             try {
                 return (T) executeBuild(build, connection);
             } catch (DaemonInitialConnectException e) {
-                LOGGER.info(e.getMessage() + " Trying a different daemon...", e.getCause());
+                //this exception means that we want to try again.
+                LOGGER.info(e.getMessage() + " Trying a different daemon...");
+            } finally {
+                connection.stop();
             }
         }
+        //TODO SF if we want to keep below sanity it should include the errors that were accumulated above.
         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;
+    protected Object executeBuild(Build build, DaemonClientConnection connection) throws DaemonInitialConnectException {
+        Object result;
         try {
             LOGGER.info("Connected to the daemon. Dispatching {} request.", build);
             connection.dispatch(build);
-            firstResult = connection.receive();
+            result = connection.receive();
         } catch (Exception e) {
-            throw new DaemonInitialConnectException("Exception when attempted to send and receive first result from the daemon.", e);
+            LOGGER.debug("Unable to perform initial dispatch/receive with the daemon.", e);
+            //We might fail hard here on the assumption that something weird happened to the daemon.
+            //However, since we haven't yet started running the build, we can recover by just trying again...
+            throw new DaemonInitialConnectException("Problem when attempted to send and receive first result from the daemon.");
+        }
+        if (result == null) {
+            throw new DaemonInitialConnectException("The first result from the daemon was empty. Most likely the process died immediately after connection.");
         }
 
-        if (firstResult instanceof BuildStarted) {
-            DaemonDiagnostics diagnostics = ((BuildStarted) firstResult).getDiagnostics();
-            return monitorBuild(build, diagnostics, connection).getValue();
-        } else if (firstResult instanceof Failure) {
+        if (result instanceof BuildStarted) {
+            DaemonDiagnostics diagnostics = ((BuildStarted) result).getDiagnostics();
+            result = monitorBuild(build, diagnostics, connection);
+        }
+
+        connection.dispatch(new Finished());
+
+        if (result 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.");
+            throw UncheckedException.throwAsUncheckedException(((Failure) result).getValue());
+        } else if (result instanceof DaemonUnavailable) {
+            throw new DaemonInitialConnectException("The daemon we connected to was unavailable: " + ((DaemonUnavailable) result).getReason());
+        } else if (result instanceof Result) {
+            return ((Result) result).getValue();
         } else {
-            throw invalidResponse(firstResult, build);
+            throw invalidResponse(result, build);
         }
     }
 
-    private Result monitorBuild(Build build, DaemonDiagnostics diagnostics, Connection<Object> connection) {
+    private Object monitorBuild(Build build, DaemonDiagnostics diagnostics, Connection<Object> connection) {
         DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, connection, executorFactory, idGenerator);
         try {
             inputForwarder.start();
@@ -162,20 +206,14 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
 
                 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);
+                    return object;
                 }
             }
         } finally {
             inputForwarder.stop();
-            connection.stop();
         }
     }
 
@@ -187,17 +225,13 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
                 + "\nBuild request sent: " + build
                 + "\nAttempting to read last messages from the daemon log...");
 
-        try {
-            LOGGER.error(diagnostics.describe());
-        } 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);
-        }
+        LOGGER.error(diagnostics.describe());
 
         throw new DaemonDisappearedException();
     }
 
     private IllegalStateException invalidResponse(Object response, Build command) {
+        //TODO SF we could include diagnostics here (they might be available).
         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/DaemonClientConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java
new file mode 100644
index 0000000..f70fc6f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.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.client;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.messaging.remote.internal.Connection;
+
+/**
+ * A simple wrapper for the connection to a daemon plus its password.
+ */
+public class DaemonClientConnection implements Connection<Object> {
+    final Connection<Object> connection;
+    private final String uid;
+    final Runnable onFailure;
+    private final static Logger LOG = Logging.getLogger(DaemonClientConnection.class);
+
+    public DaemonClientConnection(Connection<Object> connection, String uid, Runnable onFailure) {
+        this.connection = connection;
+        this.uid = uid;
+        this.onFailure = onFailure;
+    }
+
+    public void requestStop() {
+        LOG.debug("thread {}: requesting connection stop", Thread.currentThread().getId());
+        connection.requestStop();
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void dispatch(Object message) {
+        LOG.debug("thread {}: dispatching {}", Thread.currentThread().getId(), message.getClass());
+        try {
+            connection.dispatch(message);
+        } catch (Exception e) {
+            LOG.debug("Problem dispatching message to the daemon. Performing 'on failure' operation...");
+            onFailure.run();
+            throw new GradleException("Unable to dispatch the message to the daemon.", e);
+        }
+    }
+
+    public Object receive() {
+        try {
+            Object result = connection.receive();
+            LOG.debug("thread {}: received {}", Thread.currentThread().getId(), result == null ? "null" : result.getClass());
+            return result;
+        } catch (Exception e) {
+            LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation...");
+            onFailure.run();
+            throw new GradleException("Unable to receive a message from the daemon.", e);
+        }
+    }
+
+    public void stop() {
+        LOG.debug("thread {}: connection stop", Thread.currentThread().getId());
+        connection.stop();
+    }
+}
\ No newline at end of file
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
index 546a8f9..c69d113 100644
--- 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
@@ -53,7 +53,7 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
     public DaemonClientServicesSupport(ServiceRegistry loggingServices, InputStream buildStandardInput) {
         this.loggingServices = loggingServices;
         this.buildStandardInput = buildStandardInput;
-        add(new NativeServices());
+        add(NativeServices.getInstance());
     }
 
     public ServiceRegistry getLoggingServices() {
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
deleted file mode 100644
index 790880e..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.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.launcher.daemon.client;
-
-import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
-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;
-    private DaemonDiagnostics diagnostics;
-
-    public DaemonConnection(String uid, Connection<Object> connection, String password, DaemonDiagnostics diagnostics) {
-        this.uid = uid;
-        this.connection = connection;
-        this.password = password;
-        this.diagnostics = diagnostics;
-    }
-
-    public String getUid() {
-        return uid;
-    }
-
-    public Connection<Object> getConnection() {
-        return this.connection;
-    }
-
-    public String getPassword() {
-        return this.password;
-    }
-
-    /**
-     * @return diagnostics. Can be null - it means we don't have process diagnostics.
-     */
-    public DaemonDiagnostics getDaemonDiagnostics() {
-        return diagnostics;
-    }
-}
\ 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
index 595dd63..8a645c8 100644
--- 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
@@ -28,15 +28,15 @@ public interface DaemonConnector {
      *
      * @return A connection to a matching daemon, or null if none running.
      */
-    public DaemonConnection maybeConnect(ExplainingSpec<DaemonContext> constraint);
+    public DaemonClientConnection maybeConnect(ExplainingSpec<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(ExplainingSpec<DaemonContext> constraint);
+    public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint);
 
-    public DaemonConnection createConnection(ExplainingSpec<DaemonContext> constraint);
+    public DaemonClientConnection createConnection(ExplainingSpec<DaemonContext> constraint);
 
 }
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
index e3ac62e..cc38063 100644
--- 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
@@ -21,8 +21,4 @@ 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/DefaultDaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
index 33dcac4..e6661dd 100644
--- 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
@@ -21,11 +21,12 @@ import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.UncheckedException;
 import org.gradle.launcher.daemon.context.DaemonContext;
-import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
 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.Connection;
 import org.gradle.messaging.remote.internal.OutgoingConnector;
 
 import java.util.List;
@@ -59,12 +60,12 @@ public class DefaultDaemonConnector implements DaemonConnector {
         return daemonRegistry;
     }
 
-    public DaemonConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
+    public DaemonClientConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
         return findConnection(daemonRegistry.getAll(), constraint);
     }
 
-    public DaemonConnection connect(ExplainingSpec<DaemonContext> constraint) {
-        DaemonConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
+    public DaemonClientConnection connect(ExplainingSpec<DaemonContext> constraint) {
+        DaemonClientConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
         if (connection != null) {
             return connection;
         }
@@ -72,7 +73,7 @@ public class DefaultDaemonConnector implements DaemonConnector {
         return createConnection(constraint);
     }
 
-    private DaemonConnection findConnection(List<DaemonInfo> daemonInfos, ExplainingSpec<DaemonContext> constraint) {
+    private DaemonClientConnection findConnection(List<DaemonInfo> daemonInfos, ExplainingSpec<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"
@@ -82,21 +83,15 @@ public class DefaultDaemonConnector implements DaemonConnector {
             }
 
             try {
-                return connectToDaemon(daemonInfo, null);
+                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);
+                LOGGER.debug("Cannot connect to the daemon at " + daemonInfo.getAddress() + " due to " + e + ". Trying a different daemon...");
             }
         }
         return null;
     }
 
-    public DaemonConnection createConnection(ExplainingSpec<DaemonContext> constraint) {
+    public DaemonClientConnection createConnection(ExplainingSpec<DaemonContext> constraint) {
         LOGGER.info("Starting Gradle daemon");
         final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
         LOGGER.debug("Started Gradle Daemon: {}", startupInfo);
@@ -107,7 +102,7 @@ public class DefaultDaemonConnector implements DaemonConnector {
             } catch (InterruptedException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
-            DaemonConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
+            DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
             if (daemonConnection != null) {
                 return daemonConnection;
             }
@@ -116,7 +111,7 @@ public class DefaultDaemonConnector implements DaemonConnector {
         throw new GradleException("Timeout waiting to connect to Gradle daemon.\n" + startupInfo.describe());
     }
 
-    private DaemonConnection connectToDaemonWithId(DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint) throws ConnectException {
+    private DaemonClientConnection connectToDaemonWithId(DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint) 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(startupInfo.getUid())) {
@@ -126,20 +121,35 @@ public class DefaultDaemonConnector implements DaemonConnector {
                                 + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
                                 + "\n" + constraint.whyUnsatisfied(daemonInfo.getContext()));
                     }
-                    return connectToDaemon(daemonInfo, startupInfo.getDiagnostics());
+                    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.\n" + startupInfo.describe());
-                    //TODO SF after the refactorings add some coverage for visibility of the daemon tail.
+                    throw new GradleException("The forked daemon process died before we could connect.\n" + startupInfo.describe(), e);
                 }
             }
         }
         return null;
     }
 
-    private DaemonConnection connectToDaemon(DaemonInfo daemonInfo, DaemonDiagnostics diagnostics) {
-        return new DaemonConnection(daemonInfo.getContext().getUid(), connector.connect(daemonInfo.getAddress()), daemonInfo.getPassword(), diagnostics);
+    private DaemonClientConnection connectToDaemon(final DaemonInfo daemonInfo) throws ConnectException {
+        Runnable onFailure = new Runnable() {
+            public void run() {
+                LOGGER.info(DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE + daemonInfo);
+                try {
+                    daemonRegistry.remove(daemonInfo.getAddress());
+                } catch (Exception e) {
+                    //If we cannot remove then the file is corrupt or the registry is empty. We can ignore it here.
+                    LOGGER.info("Problem removing the address from the registry due to: " + e + ". It will be cleaned up later.");
+                    //TODO SF, actually we probably want always safely remove so it would be good to reduce the duplication.
+                }
+            }
+        };
+        Connection<Object> connection;
+        try {
+            connection = connector.connect(daemonInfo.getAddress());
+        } catch (ConnectException e) {
+            onFailure.run();
+            throw e;
+        }
+        return new DaemonClientConnection(connection, daemonInfo.getContext().getUid(), onFailure);
     }
 }
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
index 3f2ef08..6439db4 100755
--- 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
@@ -30,7 +30,7 @@ import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.ExecHandle;
 import org.gradle.util.Clock;
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GradleVersion;
 
 import java.io.File;
@@ -75,7 +75,7 @@ public class DefaultDaemonStarter implements DaemonStarter {
 //        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(CollectionUtils.join(File.pathSeparator, bootstrapClasspath));
         daemonArgs.add(GradleDaemon.class.getName());
         daemonArgs.add(GradleVersion.current().getVersion());
         daemonArgs.add(daemonDir.getBaseDir().getAbsolutePath());
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
index b4995c0..665780e 100644
--- 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
@@ -47,7 +47,7 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
     private final boolean displayOutput;
 
     public EmbeddedDaemonClientServices() {
-        this(LoggingServiceRegistry.newCommandLineProcessLogging(), false);
+        this(LoggingServiceRegistry.newProcessLogging(), false);
     }
 
     private class EmbeddedDaemonFactory implements Factory<Daemon> {
@@ -66,7 +66,7 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
     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"));
+                get(ProcessEnvironment.class), mgr, new File("dummy"));
     }
 
     public EmbeddedDaemonClientServices(ServiceRegistry loggingServices, boolean displayOutput) {
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
index 1cd44f4..c98470a 100644
--- 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
@@ -63,5 +63,5 @@ class EmbeddedDaemonStarter implements DaemonStarter, Stoppable {
             daemonsLock.unlock();
         }
 
-        new CompositeStoppable(daemonsToStop).stop();
+        CompositeStoppable.stoppable(daemonsToStop).stop();
     }}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java
index 7374005..b8dcd80 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java
@@ -65,7 +65,7 @@ public class InputForwarder implements Stoppable {
             }
 
             disconnectableInput = new DisconnectableInputStream(input, bufferSize);
-            outputBuffer = new LineBufferingOutputStream(forwardTo, true, bufferSize);
+            outputBuffer = new LineBufferingOutputStream(forwardTo, bufferSize);
 
             forwardingExecuter = executorFactory.create("forward input");
             forwardingExecuter.execute(new Runnable() {
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
index d590ad4..b103b94 100644
--- 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
@@ -27,7 +27,6 @@ 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;
 
@@ -50,9 +49,8 @@ public class SingleUseDaemonClient extends DaemonClient {
         LOGGER.warn("Please see the user guide chapter on the daemon at {}.", documentationRegistry.getDocumentationFor("gradle_daemon"));
         Build build = new BuildAndStop(getIdGenerator().generateId(), action, parameters);
 
-        DaemonConnection daemonConnection = getConnector().createConnection(ExplainingSpecs.<DaemonContext>satisfyAll());
-        Connection<Object> connection = daemonConnection.getConnection();
+        DaemonClientConnection daemonConnection = getConnector().createConnection(ExplainingSpecs.<DaemonContext>satisfyAll());
 
-        return (T) executeBuild(build, connection);
+        return (T) executeBuild(build, daemonConnection);
     }
 }
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
index 508910d..d77e259 100644
--- 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
@@ -18,9 +18,12 @@ package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.launcher.daemon.protocol.Failure;
+import org.gradle.launcher.daemon.protocol.Finished;
+import org.gradle.launcher.daemon.protocol.Result;
 import org.gradle.launcher.daemon.protocol.Stop;
 import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.internal.id.IdGenerator;
 
 /**
  * @author: Szczepan Faber, created at: 9/13/11
@@ -34,24 +37,20 @@ public class StopDispatcher {
     }
 
     public void dispatch(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
+        Throwable failure = null;
         try {
-            try {
-                connection.dispatch(new Stop(idGenerator.generateId()));
-            } 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);
+            connection.dispatch(new Stop(idGenerator.generateId()));
+            Result result = (Result) connection.receive();
+            if (result instanceof Failure) {
+                failure = ((Failure) result).getValue();
             }
-        } finally {
-            connection.stop();
+            connection.dispatch(new Finished());
+        } catch (Throwable e) {
+            failure = e;
+        }
+        if (failure != null) {
+            LOGGER.lifecycle("Unable to stop one of the daemons. The daemon may have crashed.");
+            LOGGER.debug(String.format("Unable to complete stop daemon using %s.", connection), failure);
         }
     }
-
 }
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
index 2c6538c..15282d4 100644
--- 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
@@ -61,7 +61,7 @@ public class DaemonDiagnostics implements Serializable {
             String tail = GFileUtils.tail(getDaemonLog(), TAIL_SIZE);
             return formatTail(tail);
         } catch (GFileUtils.TailReadingException e) {
-            return "Unable to read the tail from file: " + getDaemonLog().getAbsolutePath();
+            return "Unable to read from the daemon log file: " + getDaemonLog().getAbsolutePath() + ", because of: " + e.getCause();
         }
     }
 
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
index d066e81..3f784cb 100644
--- 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
@@ -16,11 +16,7 @@
 
 package org.gradle.launcher.daemon.logging;
 
-/**
- * by Szczepan Faber, created at: 1/19/12
- */
-public class DaemonMessages {
-    
+public abstract class DaemonMessages {
     public final static String PROCESS_STARTED = "Daemon server started.";
     public final static String ABOUT_TO_CLOSE_STREAMS = "Daemon started. About to close the streams. Daemon details: ";
     public final static String STARTED_RELAYING_LOGS = "The client will now receive all logging from the daemon (pid: ";
@@ -32,4 +28,8 @@ public class DaemonMessages {
     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...";
+    public static final String ADVERTISING_DAEMON = "Advertising the daemon address to the clients: ";
+    public static final String DAEMON_IDLE = "Daemon is idle, sleeping until state change or idle timeout at ";
+    public static final String DAEMON_BUSY = "Daemon is busy, sleeping until state changes.";
+    public static final String REMOVING_DAEMON_ADDRESS_ON_FAILURE = "Removing daemon from the registry due to communication failure. Daemon information: ";
 }
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
deleted file mode 100644
index 01faa9e..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonBusy.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.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/DaemonUnavailable.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonUnavailable.java
new file mode 100644
index 0000000..9ec7fde
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonUnavailable.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.protocol;
+
+import java.io.Serializable;
+
+/**
+ * Returned when the daemon is busy running a different command.
+ */
+public class DaemonUnavailable implements Serializable {
+    private final String reason;
+
+    public DaemonUnavailable(String reason) {
+        this.reason = reason;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Finished.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Finished.java
new file mode 100644
index 0000000..715cbe5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Finished.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.launcher.daemon.protocol;
+
+import java.io.Serializable;
+
+public class Finished implements Serializable {
+}
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
index 9d822a5..2ab1587 100644
--- 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
@@ -29,12 +29,13 @@ public class DaemonInfo implements Serializable {
     private final Address address;
     private final DaemonContext context;
     private final String password;
-    private boolean idle = true;
+    private boolean idle;
 
-    public DaemonInfo(Address address, DaemonContext context, String password) {
+    public DaemonInfo(Address address, DaemonContext context, String password, boolean idle) {
         this.address = address;
         this.context = context;
         this.password = password;
+        this.idle = idle;
     }
 
     public DaemonInfo setIdle(boolean idle) {
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
index 1b1061a..b7ee57b 100644
--- 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
@@ -30,7 +30,7 @@ public interface DaemonRegistry {
     List<DaemonInfo> getIdle();
     List<DaemonInfo> getBusy();
     
-    void store(Address address, DaemonContext daemonContext, String password);
+    void store(Address address, DaemonContext daemonContext, String password, boolean idle);
     void remove(Address address);
     void markBusy(Address address);
     void markIdle(Address address);
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
index 4fd9a79..27e05bb 100644
--- 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
@@ -45,7 +45,7 @@ public class DaemonRegistryServices extends DefaultServiceRegistry {
 
     public DaemonRegistryServices(File daemonBaseDir) {
         this(daemonBaseDir, REGISTRY_CACHE);
-        add(new NativeServices());
+        add(NativeServices.getInstance());
     }
 
     protected DaemonRegistryServices(File daemonBaseDir, Cache<File, DaemonRegistry> daemonRegistryCache) {
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
index 2fbe9bd..ac6e4ff 100644
--- 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
@@ -69,8 +69,8 @@ public class EmbeddedDaemonRegistry implements DaemonRegistry {
         return daemonInfosOfEntriesMatching(busySpec);
     }
 
-    public void store(Address address, DaemonContext daemonContext, String password) {
-        daemonInfos.put(address, new DaemonInfo(address, daemonContext, password));
+    public void store(Address address, DaemonContext daemonContext, String password, boolean idle) {
+        daemonInfos.put(address, new DaemonInfo(address, daemonContext, password, idle));
     }
 
     public void remove(Address address) {
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
index 3de9a65..7ece979 100644
--- 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
@@ -110,7 +110,9 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
-                    assertCacheNotEmpty(oldValue);
+                    if (oldValue == null) {
+                        return oldValue;
+                    }
                     oldValue.removeInfo(address);
                     return oldValue;
                 }
@@ -153,7 +155,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
         }
     }
 
-    public synchronized void store(final Address address, final DaemonContext daemonContext, final String password) {
+    public synchronized void store(final Address address, final DaemonContext daemonContext, final String password, final boolean idle) {
         lock.lock();
         LOGGER.debug("Storing daemon address: {}, context: {}", address, daemonContext);
         try {
@@ -163,7 +165,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
                         //it means the registry didn't exist yet
                         oldValue = new DaemonRegistryContent();
                     }
-                    DaemonInfo daemonInfo = new DaemonInfo(address, daemonContext, password).setIdle(true);
+                    DaemonInfo daemonInfo = new DaemonInfo(address, daemonContext, password, idle);
                     oldValue.setStatus(address, daemonInfo);
                     return oldValue;
                 }
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
index 9d3d08d..fc70cba 100644
--- 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
@@ -17,19 +17,17 @@ package org.gradle.launcher.daemon.server;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.Stoppable;
 import org.gradle.internal.concurrent.ExecutorFactory;
-import org.gradle.internal.concurrent.StoppableExecutor;
 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.remote.Address;
-import org.gradle.messaging.remote.internal.Connection;
 
 import java.util.Date;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -40,24 +38,23 @@ import java.util.concurrent.locks.ReentrantLock;
  * <p>
  * See {@link org.gradle.launcher.daemon.client.DaemonClient} for a description of the daemon communication protocol.
  */
-public class Daemon implements Runnable, Stoppable {
-
+public class Daemon implements 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 ExecutorFactory executorFactory;
     private final String password;
 
     private DaemonStateCoordinator stateCoordinator;
 
-    private final StoppableExecutor handlersExecutor;
-
     private final Lock lifecyleLock = new ReentrantLock();
 
     private Address connectorAddress;
     private DomainRegistryUpdater registryUpdater;
+    private DefaultIncomingConnectionHandler connectionHandler;
 
     /**
      * Creates a new daemon instance.
@@ -71,7 +68,7 @@ public class Daemon implements Runnable, Stoppable {
         this.daemonContext = daemonContext;
         this.password = password;
         this.commandExecuter = commandExecuter;
-        handlersExecutor = executorFactory.create("Daemon Connection Handler");
+        this.executorFactory = executorFactory;
     }
 
     public String getUid() {
@@ -94,54 +91,19 @@ public class Daemon implements Runnable, Stoppable {
             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() {
+            registryUpdater = new DomainRegistryUpdater(daemonRegistry, daemonContext, password);
+
+            Runtime.getRuntime().addShutdownHook(new Thread() {
                 public void run() {
-                    LOGGER.debug("Daemon starting at: " + new Date() + ", with address: " + connectorAddress);
-                    registryUpdater.onStart();
+                    try {
+                        daemonRegistry.remove(connectorAddress);
+                    } catch (Exception e) {
+                        LOGGER.debug("VM shutdown hook was unable to remove the daemon address from the registry. It will be cleaned up later.", e);
+                    }
                 }
-            };
-            
+            });
+
             Runnable onStartCommand = new Runnable() {
                 public void run() {
                     registryUpdater.onStartActivity();
@@ -153,25 +115,18 @@ public class Daemon implements Runnable, Stoppable {
                     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();
+            // Start the pipeline in reverse order:
+            // 1. mark daemon as running
+            // 2. start handling incoming commands
+            // 3. start accepting incoming connections
+            // 4. advertise presence in registry
+
+            stateCoordinator = new DaemonStateCoordinator(onStartCommand, onFinishCommand);
+            connectionHandler = new DefaultIncomingConnectionHandler(commandExecuter, daemonContext, stateCoordinator, executorFactory);
+            connectorAddress = connector.start(connectionHandler);
+            LOGGER.debug("Daemon starting at: " + new Date() + ", with address: " + connectorAddress);
+            registryUpdater.onStart(connectorAddress);
         } finally {
             lifecyleLock.unlock();
         }
@@ -194,46 +149,42 @@ public class Daemon implements Runnable, Stoppable {
         LOGGER.debug("stop() called on daemon");
         lifecyleLock.lock();
         try {
-            stateCoordinator.stop();
+            if (stateCoordinator == null) {
+                throw new IllegalStateException("cannot stop daemon as it has not been started.");
+            }
+
+            LOGGER.info(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP);
+
+            // Stop the pipeline:
+            // 1. mark daemon as stopped, so that any incoming requests will be rejected with 'daemon unavailable'
+            // 2. remove presence from registry
+            // 3. stop accepting new connections
+            // 4. wait for commands in progress to finish (except for abandoned long running commands, like running a build)
+
+            CompositeStoppable.stoppable(stateCoordinator, registryUpdater, connector, connectionHandler).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.
+     * Waits for the daemon to be idle for the specified number of milliseconds, then requests that the daemon stop.
      * 
      * @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);
-    }
+    public void requestStopOnIdleTimeout(int idleTimeout, TimeUnit idleTimeoutUnits) throws DaemonStoppedException {
+        LOGGER.debug("requestStopOnIdleTimeout({} {}) called on daemon", idleTimeout, idleTimeoutUnits);
+        DaemonStateCoordinator stateCoordinator;
+        lifecyleLock.lock();
+        try {
+            if (this.stateCoordinator == null) {
+                throw new IllegalStateException("cannot stop daemon as it has not been started.");
+            }
+            stateCoordinator = this.stateCoordinator;
+        } finally {
+            lifecyleLock.unlock();
+        }
 
-    /**
-     * Starts the daemon, blocking until it is stopped (either by Stop command or by another thread calling stop())
-     */
-    public void run() {
-        start();
-        awaitStop();
+        stateCoordinator.stopOnIdleTimeout(idleTimeout, idleTimeoutUnits);
     }
-
 }
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
index 282c2fd..9a7ed56 100644
--- 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
@@ -34,7 +34,7 @@ public interface DaemonServerConnector extends Stoppable {
      * @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);
+    Address start(IncomingConnectionHandler handler);
 
     /**
      * Stops accepting new connections, and blocks until all active connections close.
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
index c1a0590..4c61fae 100644
--- 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
@@ -51,7 +51,7 @@ public class DaemonServices extends DefaultServiceRegistry {
         this.loggingServices = loggingServices;
         this.loggingManager = loggingManager;
 
-        add(new NativeServices());
+        add(NativeServices.getInstance());
         add(new DaemonRegistryServices(configuration.getBaseDir()));
     }
 
@@ -87,7 +87,6 @@ public class DaemonServices extends DefaultServiceRegistry {
                 "password",
                 new DefaultDaemonCommandExecuter(
                         new DefaultGradleLauncherFactory(loggingServices),
-                        get(ExecutorFactory.class),
                         get(ProcessEnvironment.class),
                         loggingManager,
                         getDaemonLogFile()),
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
index 082a93c..735f429 100644
--- 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
@@ -19,11 +19,13 @@ 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.internal.concurrent.DefaultExecutorFactory;
-import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.server.exec.DaemonStateControl;
+import org.gradle.launcher.daemon.server.exec.DaemonUnavailableException;
+import org.slf4j.Logger;
 
 import java.util.Date;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -31,98 +33,67 @@ 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 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, DaemonStateControl {
+    private static final Logger LOGGER = Logging.getLogger(DaemonStateCoordinator.class);
 
-    private static final org.gradle.api.logging.Logger LOGGER = Logging.getLogger(DaemonStateCoordinator.class);
+    private enum State {Running, StopRequested, Stopped, Broken}
 
     private final Lock lock = new ReentrantLock();
-    Condition condition = lock.newCondition();
+    private final Condition condition = lock.newCondition();
 
-    private boolean stoppingOrStopped;
-    private boolean stopped;
+    private State state = State.Running;
     private long lastActivityAt = -1;
-    private DaemonCommandExecution currentCommandExecution;
+    private String currentCommandExecution;
 
-    private final Runnable onStart;
     private final Runnable onStartCommand;
     private final Runnable onFinishCommand;
-    private final Runnable onStop;
-    private final Runnable onStopRequested;
+    private Runnable onDisconnect;
 
-    public DaemonStateCoordinator(Runnable onStart, Runnable onStartCommand, Runnable onFinishCommand, Runnable onStop, Runnable onStopRequested) {
-        this.onStart = onStart;
+    public DaemonStateCoordinator(Runnable onStartCommand, Runnable onFinishCommand) {
         this.onStartCommand = onStartCommand;
         this.onFinishCommand = onFinishCommand;
-        this.onStop = onStop;
-        this.onStopRequested = onStopRequested;
+        updateActivityTimestamp();
     }
 
-    /**
-     * 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();
-        }
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
     }
 
-    /**
-     * Waits until stopped, or timeout.
-     *
-     * @return true if stopped, false if timeout
-     */
-    public boolean awaitStopOrIdleTimeout(int timeout) {
+    private boolean awaitStop(long timeoutMs) {
         lock.lock();
         try {
-            LOGGER.debug("waiting for daemon to stop or be idle for {}ms", timeout);
+            LOGGER.debug("waiting for daemon to stop or be idle for {}ms", timeoutMs);
             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");
+                    switch (state) {
+                        case Running:
+                            if (isBusy()) {
+                                LOGGER.debug(DaemonMessages.DAEMON_BUSY);
+                                condition.await();
+                            } else if (hasBeenIdleFor(timeoutMs)) {
+                                LOGGER.debug("Daemon has been idle for requested period.");
+                                stop();
+                                return false;
+                            } else {
+                                Date waitUntil = new Date(lastActivityAt + timeoutMs);
+                                LOGGER.debug(DaemonMessages.DAEMON_IDLE + waitUntil);
+                                condition.awaitUntil(waitUntil);
+                            }
+                            break;
+                        case Broken:
+                            throw new IllegalStateException("This daemon is in a broken state.");
+                        case StopRequested:
+                            LOGGER.debug("Daemon is stopping, sleeping until state changes.");
+                            condition.await();
+                            break;
+                        case Stopped:
+                            LOGGER.debug("Daemon has stopped.");
+                            return true;
                     }
                 } catch (InterruptedException e) {
                     throw UncheckedException.throwAsUncheckedException(e);
@@ -133,30 +104,20 @@ public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
         }
     }
 
-    public void awaitIdleTimeout(int timeout) throws DaemonStoppedException {
-        if (awaitStopOrIdleTimeout(timeout)) {
+    public void stopOnIdleTimeout(int timeout, TimeUnit timeoutUnits) throws DaemonStoppedException {
+        if (awaitStop(timeoutUnits.toMillis(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() {
+    public void requestStop() {
         lock.lock();
         try {
             LOGGER.debug("Stop as soon as idle requested. The daemon is busy: " + isBusy());
             if (isBusy()) {
-                onStopRequested.run();
-                stoppingOrStopped = true;
+                beginStopping();
             } else {
-               stop();
+                stop();
             }
         } finally {
             lock.unlock();
@@ -166,113 +127,125 @@ public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
     /**
      * 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.
+     * 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()
+     * @see #requestStop()
      */
     public void stop() {
         lock.lock();
         try {
             LOGGER.debug("Stop requested. The daemon is running a build: " + isBusy());
-            if (!isStoppingOrStopped()) {
-                onStopRequested.run();
+            switch (state) {
+                case Running:
+                case Broken:
+                case StopRequested:
+                    setState(State.Stopped);
+                    break;
+                case Stopped:
+                    break;
+                default:
+                    throw new IllegalStateException("Daemon is in unexpected state: " + state);
             }
-            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();
-                }
-            });
+    private void beginStopping() {
+        switch (state) {
+            case Running:
+            case Broken:
+                setState(State.StopRequested);
+                break;
+            case StopRequested:
+            case Stopped:
+                break;
+            default:
+                throw new IllegalStateException("Daemon is in unexpected state: " + state);
         }
-    };
+    }
 
-    /**
-     * @return returns false if the daemon was already requested to stop
-     */
-    public boolean requestStop() {
+    public void requestForcefulStop() {
         lock.lock();
         try {
-            if (stoppingOrStopped) {
-                return false;
+            try {
+                if (onDisconnect != null) {
+                    onDisconnect.run();
+                }
+            } finally {
+                stop();
             }
-            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) {
+    public void runCommand(Runnable command, String commandDisplayName, Runnable onCommandAbandoned) throws DaemonUnavailableException {
+        onStartCommand(commandDisplayName, onCommandAbandoned);
+        try {
+            command.run();
+        } finally {
+            onFinishCommand();
+        }
+    }
+
+    private void onStartCommand(String commandDisplayName, Runnable onDisconnect) {
         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();
+            switch (state) {
+                case Broken:
+                    throw new DaemonUnavailableException("This daemon is in a broken state and will stop.");
+                case StopRequested:
+                    throw new DaemonUnavailableException("This daemon is currently stopping.");
+                case Stopped:
+                    throw new DaemonUnavailableException("This daemon has stopped.");
+            }
+            if (currentCommandExecution != null) {
+                throw new DaemonUnavailableException(String.format("This daemon is currently executing: %s", currentCommandExecution));
+            }
+
+            LOGGER.debug("onStartCommand({}) called after {} minutes of idle", commandDisplayName, getIdleMinutes());
+            try {
                 onStartCommand.run();
+                currentCommandExecution = commandDisplayName;
+                this.onDisconnect = onDisconnect;
+                updateActivityTimestamp();
                 condition.signalAll();
-                return null;
+            } catch (Throwable throwable) {
+                setState(State.Broken);
+                throw UncheckedException.throwAsUncheckedException(throwable);
             }
         } 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() {
+    private void 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()) {
+            String execution = currentCommandExecution;
+            LOGGER.debug("onFinishCommand() called while execution = {}", execution);
+            currentCommandExecution = null;
+            onDisconnect = null;
+            updateActivityTimestamp();
+            switch (state) {
+                case Running:
+                    try {
+                        onFinishCommand.run();
+                        condition.signalAll();
+                    } catch (Throwable throwable) {
+                        setState(State.Broken);
+                        throw UncheckedException.throwAsUncheckedException(throwable);
+                    }
+                    break;
+                case StopRequested:
                     stop();
-                } else {
-                    onFinishCommand.run();
-                    condition.signalAll();
-                }
+                    break;
+                case Stopped:
+                    break;
+                default:
+                    throw new IllegalStateException("Daemon is in unexpected state: " + state);
             }
-
-            return execution;
         } finally {
             lock.unlock();
         }
@@ -284,64 +257,32 @@ public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
         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() {
+    private double getIdleMinutes() {
         lock.lock();
         try {
-            if (isStarted()) {
-                return (System.currentTimeMillis() - lastActivityAt) / 1000 / 60;
-            } else {
-                return -1;
-            }
+            return (System.currentTimeMillis() - lastActivityAt) / 1000 / 60;
         } finally {
             lock.unlock();
         }
     }
 
-    public boolean hasBeenIdleFor(int milliseconds) {
-        if (!isStarted()) {
-            return false;
-        } else {
-            return lastActivityAt < (System.currentTimeMillis() - milliseconds);
-        }
+    private boolean hasBeenIdleFor(long milliseconds) {
+        return lastActivityAt < (System.currentTimeMillis() - milliseconds);
     }
 
-    public boolean isStopped() {
-        return stopped;
+    boolean isStopped() {
+        return state == State.Stopped;
     }
 
-    public boolean isStoppingOrStopped() {
-        return stoppingOrStopped;
+    boolean isStoppingOrStopped() {
+        return state == State.Stopped || state == State.StopRequested;
     }
 
-    public boolean isIdle() {
-        return isRunning() && currentCommandExecution == null;
+    boolean isIdle() {
+        return currentCommandExecution == null;
     }
 
-    public boolean isBusy() {
-        return isRunning() && !isIdle();
+    boolean isBusy() {
+        return !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
index 1853c28..bd097c2 100644
--- 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
@@ -15,22 +15,16 @@
  */
 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;
+public class DaemonStoppedException extends RuntimeException {
+    public DaemonStoppedException(String operationDisplayName) {
+        super(toMessage(operationDisplayName));
     }
 
-    private static String toMessage(DaemonCommandExecution executionWhenStopped) {
-        if (executionWhenStopped == null) {
+    private static String toMessage(String operationDisplayName) {
+        if (operationDisplayName == null) {
             return "daemon explicitly stopped while idle";
         } else {
-            return String.format("daemon explicitly stopped while busy, execution when stopped = %s", executionWhenStopped);
+            return String.format("daemon explicitly stopped while busy, execution when stopped = %s", operationDisplayName);
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultDaemonConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultDaemonConnection.java
new file mode 100644
index 0000000..b12f0eb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultDaemonConnection.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.launcher.daemon.protocol.*;
+import org.gradle.launcher.daemon.server.exec.DaemonConnection;
+import org.gradle.launcher.daemon.server.exec.StdinHandler;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.messaging.remote.internal.Connection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+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 DefaultDaemonConnection implements DaemonConnection {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDaemonConnection.class);
+    private final Connection<Object> connection;
+    private final StoppableExecutor executor;
+    private final StdinQueue stdinQueue;
+    private final DisconnectQueue disconnectQueue;
+    private final ReceiveQueue receiveQueue;
+
+    public DefaultDaemonConnection(final Connection<Object> connection, ExecutorFactory executorFactory) {
+        this.connection = connection;
+        stdinQueue = new StdinQueue(executorFactory);
+        disconnectQueue = new DisconnectQueue();
+        receiveQueue = new ReceiveQueue();
+        executor = executorFactory.create("Handler for " + connection.toString());
+        executor.execute(new Runnable() {
+            public void run() {
+                Throwable failure = null;
+                try {
+                    while (true) {
+                        Object message;
+                        try {
+                            message = connection.receive();
+                        } catch (Exception e) {
+                            LOGGER.debug("Could not receive message from client.", e);
+                            failure = e;
+                            return;
+                        }
+                        if (message == null) {
+                            LOGGER.debug("Received end-of-input from client.");
+                            return;
+                        }
+
+                        if (!(message instanceof IoCommand)) {
+                            LOGGER.debug("Received non-IO message from client: {}", message);
+                            receiveQueue.add(message);
+                        } else {
+                            LOGGER.debug("Received IO message from client: {}", message);
+                            stdinQueue.add((IoCommand) message);
+                        }
+                    }
+                } finally {
+                    stdinQueue.disconnect();
+                    disconnectQueue.disconnect();
+                    receiveQueue.disconnect(failure);
+                }
+            }
+        });
+    }
+
+    public void onStdin(StdinHandler handler) {
+        stdinQueue.useHandler(handler);
+    }
+
+    public void onDisconnect(Runnable handler) {
+        disconnectQueue.useHandler(handler);
+    }
+
+    public Object receive(long timeoutValue, TimeUnit timeoutUnits) {
+        return receiveQueue.take(timeoutValue, timeoutUnits);
+    }
+
+    public void daemonUnavailable(DaemonUnavailable unavailable) {
+        connection.dispatch(unavailable);
+    }
+
+    public void buildStarted(BuildStarted buildStarted) {
+        connection.dispatch(buildStarted);
+    }
+
+    public void logEvent(OutputEvent logEvent) {
+        connection.dispatch(logEvent);
+    }
+
+    public void completed(Result result) {
+        connection.dispatch(result);
+    }
+
+    public void stop() {
+        // 1. Stop handling disconnects. Blocks until the handler has finished.
+        // 2. Stop the connection. This means that the thread receiving from the connection will receive a null and finish up.
+        // 3. Stop receiving incoming messages. Blocks until the receive thread has finished. This will notify the stdin and receive queues to signal end of input.
+        // 4. Stop the receive queue, to unblock any threads blocked in receive().
+        // 5. Stop handling stdin. Blocks until the handler has finished. Discards any queued input.
+        CompositeStoppable.stoppable(disconnectQueue, connection, executor, receiveQueue, stdinQueue).stop();
+    }
+
+    private static class StdinQueue implements Stoppable {
+        private final Lock lock = new ReentrantLock();
+        private final Condition condition = lock.newCondition();
+        private final LinkedList<IoCommand> stdin = new LinkedList<IoCommand>();
+        private StoppableExecutor executor;
+        private boolean removed;
+        private final ExecutorFactory executorFactory;
+
+        private StdinQueue(ExecutorFactory executorFactory) {
+            this.executorFactory = executorFactory;
+        }
+
+        public void stop() {
+            StoppableExecutor executor;
+            lock.lock();
+            try {
+                executor = this.executor;
+            } finally {
+                lock.unlock();
+            }
+            if (executor != null) {
+                executor.stop();
+            }
+        }
+
+        public void add(IoCommand command) {
+            lock.lock();
+            try {
+                stdin.add(command);
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void useHandler(final StdinHandler handler) {
+            if (handler != null) {
+                startConsuming(handler);
+            } else {
+                stopConsuming();
+            }
+        }
+
+        private void stopConsuming() {
+            StoppableExecutor executor;
+            lock.lock();
+            try {
+                stdin.clear();
+                removed = true;
+                condition.signalAll();
+                executor = this.executor;
+            } finally {
+                lock.unlock();
+            }
+            if (executor != null) {
+                executor.stop();
+            }
+        }
+
+        private void startConsuming(final StdinHandler handler) {
+            lock.lock();
+            try {
+                if (executor != null) {
+                    throw new UnsupportedOperationException("Multiple stdin handlers not supported.");
+                }
+                executor = executorFactory.create("Stdin handler");
+                executor.execute(new Runnable() {
+                    public void run() {
+                        while (true) {
+                            IoCommand command;
+                            lock.lock();
+                            try {
+                                while (!removed && stdin.isEmpty()) {
+                                    try {
+                                        condition.await();
+                                    } catch (InterruptedException e) {
+                                        throw UncheckedException.throwAsUncheckedException(e);
+                                    }
+                                }
+                                if (removed) {
+                                    return;
+                                }
+                                command = stdin.removeFirst();
+                            } finally {
+                                lock.unlock();
+                            }
+                            try {
+                                if (command instanceof CloseInput) {
+                                    handler.onEndOfInput();
+                                    return;
+                                } else {
+                                    handler.onInput((ForwardInput) command);
+                                }
+                            } catch (Exception e) {
+                                LOGGER.warn("Could not forward client stdin.", e);
+                                return;
+                            }
+                        }
+                    }
+                });
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void disconnect() {
+            lock.lock();
+            try {
+                stdin.clear();
+                stdin.add(new CloseInput("<disconnected>"));
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private static class DisconnectQueue implements Stoppable {
+        private final Lock lock = new ReentrantLock();
+        private final Condition condition = lock.newCondition();
+        private Runnable handler;
+        private boolean notifying;
+        private boolean disconnected;
+
+        public void disconnect() {
+            Runnable action;
+            lock.lock();
+            try {
+                disconnected = true;
+                if (handler == null) {
+                    return;
+                }
+                action = handler;
+                notifying = true;
+            } finally {
+                lock.unlock();
+            }
+            runAction(action);
+        }
+
+        private void runAction(Runnable action) {
+            try {
+                action.run();
+            } catch (Exception e) {
+                LOGGER.warn("Failed to notify disconnect handler.", e);
+            } finally {
+                lock.lock();
+                try {
+                    notifying = false;
+                    condition.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+
+        public void stop() {
+            useHandler(null);
+        }
+
+        public void useHandler(Runnable handler) {
+            if (handler != null) {
+                startMonitoring(handler);
+            } else {
+                stopMonitoring();
+            }
+        }
+
+        private void startMonitoring(Runnable handler) {
+            Runnable action;
+
+            lock.lock();
+            try {
+                if (this.handler != null) {
+                    throw new UnsupportedOperationException("Multiple disconnect handlers not supported.");
+                }
+                this.handler = handler;
+                if (!disconnected) {
+                    return;
+                }
+                action = handler;
+                notifying = true;
+            } finally {
+                lock.unlock();
+            }
+
+            runAction(action);
+        }
+
+        private void stopMonitoring() {
+            lock.lock();
+            try {
+                while (notifying) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+                handler = null;
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private static class ReceiveQueue implements Stoppable {
+        private static final Object END = new Object();
+        private final BlockingQueue<Object> queue = new LinkedBlockingQueue<Object>();
+
+        public void stop() {
+        }
+
+        public void disconnect(Throwable failure) {
+            queue.clear();
+            if (failure != null) {
+                add(failure);
+            }
+            add(END);
+        }
+
+        public void add(Object message) {
+            try {
+                queue.put(message);
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        public Object take(long timeoutValue, TimeUnit timeoutUnits) {
+            Object result;
+            try {
+                result = queue.poll(timeoutValue, timeoutUnits);
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+            if (result instanceof Throwable) {
+                Throwable failure = (Throwable) result;
+                throw UncheckedException.throwAsUncheckedException(failure);
+            }
+            return result == END ? null : result;
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultIncomingConnectionHandler.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultIncomingConnectionHandler.java
new file mode 100644
index 0000000..df71d2d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DefaultIncomingConnectionHandler.java
@@ -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.launcher.daemon.server;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+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.server.exec.DaemonCommandExecuter;
+import org.gradle.launcher.daemon.server.exec.DaemonConnection;
+import org.gradle.launcher.daemon.server.exec.DaemonStateControl;
+import org.gradle.messaging.remote.internal.Connection;
+
+import java.util.HashSet;
+import java.util.Set;
+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 DefaultIncomingConnectionHandler implements IncomingConnectionHandler, Stoppable {
+    private static final Logger LOGGER = Logging.getLogger(DefaultIncomingConnectionHandler.class);
+    private final StoppableExecutor workers;
+    private final DaemonContext daemonContext;
+    private final DaemonCommandExecuter commandExecuter;
+    private final DaemonStateControl daemonStateControl;
+    private final ExecutorFactory executorFactory;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Set<Connection<?>> inProgress = new HashSet<Connection<?>>();
+
+    public DefaultIncomingConnectionHandler(DaemonCommandExecuter commandExecuter, DaemonContext daemonContext, DaemonStateControl daemonStateControl, ExecutorFactory executorFactory) {
+        this.commandExecuter = commandExecuter;
+        this.daemonContext = daemonContext;
+        this.daemonStateControl = daemonStateControl;
+        this.executorFactory = executorFactory;
+        workers = executorFactory.create("Daemon");
+    }
+
+    public void handle(final Connection<Object> connection) {
+        // Mark the connection has being handled
+        onStartHandling(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
+
+        workers.execute(new ConnectionWorker(connection));
+    }
+
+    private void onStartHandling(Connection<Object> connection) {
+        lock.lock();
+        try {
+            inProgress.add(connection);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onFinishHandling(Connection<Object> connection) {
+        lock.lock();
+        try {
+            inProgress.remove(connection);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Blocks until all connections have been handled or abandoned.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            while (!inProgress.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private class ConnectionWorker implements Runnable {
+        private final Connection<Object> connection;
+
+        public ConnectionWorker(Connection<Object> connection) {
+            this.connection = connection;
+        }
+
+        public void run() {
+            try {
+                receiveAndHandleCommand();
+            } finally {
+                onFinishHandling(connection);
+            }
+        }
+
+        private void receiveAndHandleCommand() {
+            try {
+                DefaultDaemonConnection daemonConnection = new DefaultDaemonConnection(connection, executorFactory);
+                try {
+                    Command command = receiveCommand(daemonConnection);
+                    if (command != null) {
+                        handleCommand(command, daemonConnection);
+                    }
+                } finally {
+                    daemonConnection.stop();
+                }
+            } finally {
+                connection.stop();
+            }
+        }
+
+        private Command receiveCommand(DaemonConnection daemonConnection) {
+            try {
+                Command command = (Command) daemonConnection.receive(120, TimeUnit.SECONDS);
+                LOGGER.info("Received command: {}.", command);
+                return 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);
+                daemonConnection.completed(new DaemonFailure(new RuntimeException(message, e)));
+                //TODO SF exception handling / send typed exception / refactor / unit test and apply the same for below
+                return null;
+            }
+        }
+
+        private void handleCommand(Command command, DaemonConnection daemonConnection) {
+            LOGGER.debug(DaemonMessages.STARTED_EXECUTING_COMMAND + command + " with connection: " + connection + ".");
+            try {
+                commandExecuter.executeCommand(daemonConnection, command, daemonContext, daemonStateControl, new Runnable() {
+                    public void run() {
+                        onFinishHandling(connection);
+                    }
+                });
+            } 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);
+                daemonConnection.completed(new DaemonFailure(new RuntimeException(message, e)));
+            } finally {
+                LOGGER.debug(DaemonMessages.FINISHED_EXECUTING_COMMAND + command);
+            }
+
+            Object finished = daemonConnection.receive(60, TimeUnit.SECONDS);
+            LOGGER.debug("Received finished message: {}", finished);
+        }
+    }
+}
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
index d10190a..20ff490 100644
--- 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
@@ -18,27 +18,28 @@ 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.registry.DaemonRegistry;
 import org.gradle.messaging.remote.Address;
 
 /**
 * @author: Szczepan Faber, created at: 9/12/11
 */
-class DomainRegistryUpdater {
+class DomainRegistryUpdater implements Stoppable {
 
     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;
+    private Address connectorAddress;
 
-    public DomainRegistryUpdater(DaemonRegistry daemonRegistry, DaemonContext daemonContext, String password, Address connectorAddress) {
+    public DomainRegistryUpdater(DaemonRegistry daemonRegistry, DaemonContext daemonContext, String password) {
         this.daemonRegistry = daemonRegistry;
         this.daemonContext = daemonContext;
         this.password = password;
-        this.connectorAddress = connectorAddress;
     }
 
     public void onStartActivity() {
@@ -59,14 +60,14 @@ class DomainRegistryUpdater {
         }
     }
 
-    public void onStart() {
-        LOGGER.info("Advertising the daemon address to the clients: {}", connectorAddress);
+    public void onStart(Address connectorAddress) {
+        LOGGER.info(DaemonMessages.ADVERTISING_DAEMON + connectorAddress);
         LOGGER.debug("Advertised daemon context: {}", daemonContext);
-        daemonRegistry.store(connectorAddress, daemonContext, password);
-        daemonRegistry.markBusy(connectorAddress);
+        this.connectorAddress = connectorAddress;
+        daemonRegistry.store(connectorAddress, daemonContext, password, false);
     }
 
-    public void onStop() {
+    public void stop() {
         LOGGER.debug("Removing our presence to clients, eg. removing this address from the registry: " + connectorAddress);
         try {
             daemonRegistry.remove(connectorAddress);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java
index f43eaf6..255d460 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java
@@ -17,7 +17,10 @@
 package org.gradle.launcher.daemon.server;
 
 import org.gradle.internal.concurrent.Synchronizer;
+import org.gradle.logging.internal.OutputEvent;
 import org.gradle.messaging.remote.internal.Connection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Connection decorator that synchronizes dispatching.
@@ -25,6 +28,7 @@ import org.gradle.messaging.remote.internal.Connection;
  * by Szczepan Faber, created at: 2/27/12
  */
 public class SynchronizedDispatchConnection<T> implements Connection<T> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SynchronizedDispatchConnection.class);
     
     private final Synchronizer sync = new Synchronizer();
     private final Connection<T> delegate;
@@ -34,10 +38,14 @@ public class SynchronizedDispatchConnection<T> implements Connection<T> {
     }
     
     public void requestStop() {
+        LOGGER.debug("thread {}: requesting stop for connection", Thread.currentThread().getId());
         delegate.requestStop();
     }
 
     public void dispatch(final T message) {
+        if (!(message instanceof OutputEvent)) {
+            LOGGER.debug("thread {}: dispatching {}", Thread.currentThread().getId(), message.getClass());
+        }
         sync.synchronize(new Runnable() {
             public void run() {
                 delegate.dispatch(message);
@@ -48,10 +56,13 @@ public class SynchronizedDispatchConnection<T> implements Connection<T> {
     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();
+        T result = delegate.receive();
+        LOGGER.debug("thread {}: received {}", Thread.currentThread().getId(), result == null ? "null" : result.getClass());
+        return result;
     }
 
     public void stop() {
+        LOGGER.debug("thread {}: stopping connection", Thread.currentThread().getId());
         delegate.stop();
     }
 
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
index 897fbeb..15a3ec0 100644
--- 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
@@ -32,7 +32,7 @@ public class CatchAndForwardDaemonFailure implements DaemonCommandAction {
             execution.proceed();
         } catch (Throwable e) {
             LOGGER.error(String.format("Daemon failure during execution of %s - ", execution.getCommand()), e);
-            execution.getConnection().dispatch(new DaemonFailure(e));
+            execution.getConnection().completed(new DaemonFailure(e));
         }
     }
 }
\ 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
index 6db1b4b..512e4e7 100644
--- 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
@@ -17,7 +17,6 @@ package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.messaging.remote.internal.Connection;
 
 /**
  * An object capable of responding to commands sent to a daemon.
@@ -36,5 +35,5 @@ public interface DaemonCommandExecuter {
      * <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, DaemonStateControl daemonStateControl);
+    void executeCommand(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, Runnable commandAbandoned);
 }
\ 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
index 8020d83..c4e41bd 100644
--- 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
@@ -17,7 +17,6 @@ package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.messaging.remote.internal.DisconnectAwareConnection;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -32,26 +31,28 @@ import java.util.List;
  */
 public class DaemonCommandExecution {
 
-    final private DisconnectAwareConnection<Object> connection;
+    final private DaemonConnection connection;
     final private Command command;
     final private DaemonContext daemonContext;
     final private DaemonStateControl daemonStateControl;
+    final private Runnable commandAbandoned;
     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, DaemonStateControl daemonStateControl, List<DaemonCommandAction> actions) {
+    public DaemonCommandExecution(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, Runnable commandAbandoned, List<DaemonCommandAction> actions) {
         this.connection = connection;
         this.command = command;
         this.daemonContext = daemonContext;
         this.daemonStateControl = daemonStateControl;
+        this.commandAbandoned = commandAbandoned;
 
         this.actions = new LinkedList<DaemonCommandAction>(actions);
     }
 
-    public DisconnectAwareConnection<Object> getConnection() {
+    public DaemonConnection getConnection() {
         return connection;
     }
 
@@ -72,6 +73,10 @@ public class DaemonCommandExecution {
         return daemonStateControl;
     }
 
+    public Runnable getCommandAbandonedHandler() {
+        return commandAbandoned;
+    }
+
     /**
      * Sets what is to be considered the result of executing the command.
      * <p>
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonConnection.java
new file mode 100644
index 0000000..2cf02b9
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonConnection.java
@@ -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.launcher.daemon.server.exec;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.Stoppable;
+import org.gradle.launcher.daemon.protocol.BuildStarted;
+import org.gradle.launcher.daemon.protocol.DaemonUnavailable;
+import org.gradle.launcher.daemon.protocol.Result;
+import org.gradle.logging.internal.OutputEvent;
+
+import java.util.concurrent.TimeUnit;
+
+public interface DaemonConnection extends Stoppable {
+    /**
+     * Registers a handler for incoming client stdin. The handler is notified from at most one thread at a time.
+     *
+     * The following events trigger and end of input:
+     * <ul>
+     * <li>A {@link org.gradle.launcher.daemon.protocol.CloseInput} event received from the client.</li>
+     * <li>When the client connection disconnects unexpectedly.</li>
+     * <li>When the connection is closed using {@link #stop()}.</li>
+     * </ul>
+     *
+     * Note: the end of input may be signalled from another thread before this method returns.
+     *
+     * @param handler the handler. Use null to remove the current handler.
+     */
+    void onStdin(@Nullable StdinHandler handler);
+
+    /**
+     * Registers a handler for when this connection is disconnected unexpectedly.. The handler is notified at most once.
+     *
+     * The handler is not notified after any of the following occurs:
+     * <ul>
+     * <li>When the connection is closed using {@link #stop()}.</li>
+     * </ul>
+     *
+     * Note: the handler may be run from another thread before this method returns.
+     *
+     * @param handler the handler. Use null to remove the current handler.
+     */
+    void onDisconnect(@Nullable Runnable handler);
+
+    /**
+     * Dispatches a daemon unavailable message to the client.
+     */
+    void daemonUnavailable(DaemonUnavailable unavailable);
+
+    /**
+     * Dispatches a build started message to the client.
+     */
+    void buildStarted(BuildStarted buildStarted);
+
+    /**
+     * Dispatches a log event message to the client.
+     */
+    void logEvent(OutputEvent logEvent);
+
+    /**
+     * Dispatches the given result to the client.
+     */
+    void completed(Result result);
+
+    /**
+     * Receives a message from the client. Does not include any stdin messages. Blocks until a message is received or the connection is closed or the timeout is reached.
+     *
+     * @return null On end of connection or timeout.
+     */
+    Object receive(long timeoutValue, TimeUnit timeoutUnits);
+
+    /**
+     * Blocks until all handlers have been notified and any queued messages have been dispatched to the client.
+     */
+    void stop();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java
index 5ad1e6c..6ea21a2 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java
@@ -18,37 +18,29 @@ package org.gradle.launcher.daemon.server.exec;
 
 public interface DaemonStateControl {
     /**
-     * Perform a stop, but wait until the daemon is idle to cut any open connections.
+     * <p>Requests that the daemon stop, but wait until the daemon is idle. The stop will happen asynchronously, and this method does not block.
      *
-     * 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.
-     */
-    void stopAsSoonAsIdle();
-
-    /**
-     * @return returns false if the daemon was already requested to stop
+     * <p>The daemon will stop accepting new work, so that subsequent calls to {@link #runCommand} will failing with {@link DaemonUnavailableException}.
      */
-    boolean requestStop();
+    void requestStop();
 
     /**
-     * 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.
+     * Requests a forceful stops of the daemon. Does not wait until the daemon is idle to begin stopping. The stop will happen asynchronously, and this method does not block.
+     *
+     * <p>If any long running command is currently running, the operation's abandoned command handler will be executed.</p>
+     *
+     * <p>The daemon will stop accepting new work, so that subsequent calls to {@link #runCommand} will failing with {@link DaemonUnavailableException}.
      */
-    DaemonCommandExecution onStartCommand(DaemonCommandExecution execution);
+    void requestForcefulStop();
 
     /**
-     * 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 stops.
+     * Runs the given long running command. No more than 1 command may be running at any given time.
+     *
+     * @param command The command to run
+     * @param commandDisplayName The command's display name, used for logging and error messages.
+     * @param onCommandAbandoned An action to run with a forceful stop is requested using {@link #requestForcefulStop()}, to notify the caller that the operation is being abandoned.
+     *
+     * @throws DaemonUnavailableException If this daemon is currently executing another command or a stop has been requested.
      */
-    DaemonCommandExecution onFinishCommand();
+    void runCommand(Runnable command, String commandDisplayName, Runnable onCommandAbandoned) throws DaemonUnavailableException;
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonUnavailableException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonUnavailableException.java
new file mode 100644
index 0000000..0855bc9
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonUnavailableException.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.launcher.daemon.server.exec;
+
+/**
+ * Thrown when the daemon is unavailable to run any commands.
+ */
+public class DaemonUnavailableException extends RuntimeException {
+    public DaemonUnavailableException(String message) {
+        super(message);
+    }
+}
+
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
index 75eec96..b05f1d6 100644
--- 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
@@ -16,14 +16,12 @@
 package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.initialization.GradleLauncherFactory;
-import org.gradle.internal.concurrent.ExecutorFactory;
 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.logging.LoggingManagerInternal;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.DisconnectAwareConnectionDecorator;
+import org.gradle.logging.internal.LoggingOutputInternal;
 
 import java.io.File;
 import java.util.Arrays;
@@ -34,28 +32,25 @@ 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 LoggingOutputInternal loggingOutput;
     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;
+    public DefaultDaemonCommandExecuter(GradleLauncherFactory launcherFactory, ProcessEnvironment processEnvironment, LoggingManagerInternal loggingOutput, File daemonLog) {
         this.processEnvironment = processEnvironment;
         this.daemonLog = daemonLog;
-        this.loggingManager = loggingManager;
+        this.loggingOutput = loggingOutput;
         this.launcherFactory = launcherFactory;
     }
 
-    public void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl) {
+    public void executeCommand(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, Runnable commandAbandoned) {
         new DaemonCommandExecution(
-            new DisconnectAwareConnectionDecorator<Object>(connection, executorFactory.create("DefaultDaemonCommandExecuter > DisconnectAwareConnectionDecorator")),
+            connection,
             command,
             daemonContext,
             daemonStateControl,
+            commandAbandoned,
             createActions(daemonContext)
         ).proceed();
     }
@@ -63,14 +58,12 @@ public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
     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 LogToClient(loggingOutput, daemonDiagnostics), // from this point down, logging is sent back to the client
+            new ForwardClientInput(),
             new ReturnResult(),
             new StartStopIfBuildAndStop(),
             new ResetDeprecationLogger(),
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
index 9e35888..406170c 100644
--- 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
@@ -17,6 +17,7 @@ package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.SystemProperties;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.launcher.daemon.protocol.Build;
 import org.gradle.util.GFileUtils;
@@ -29,9 +30,10 @@ 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);
 
+    private final ProcessEnvironment processEnvironment;
+
     public EstablishBuildEnvironment(ProcessEnvironment processEnvironment) {
         this.processEnvironment = processEnvironment;
     }
@@ -39,19 +41,15 @@ public class EstablishBuildEnvironment extends BuildCommandOnly {
     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"));
+        Map<String, String> originalEnv = System.getenv();
+        File originalProcessDir = GFileUtils.canonicalise(new File("."));
 
-        System.setProperties(clientSystemProperties);
+        for (Map.Entry<String, String> entry : build.getParameters().getSystemProperties().entrySet()) {
+            if (SystemProperties.getStandardProperties().contains(entry.getKey())) { continue; }
+            if (entry.getKey().startsWith("sun.")) { continue; }
+            System.setProperty(entry.getKey(), entry.getValue());
+        }
 
-        Map<String, String> originalEnv = System.getenv();
         LOGGER.debug("Configuring env variables: {}", build.getParameters().getEnvVariables());
         processEnvironment.maybeSetEnvironment(build.getParameters().getEnvVariables());
 
@@ -62,8 +60,7 @@ public class EstablishBuildEnvironment extends BuildCommandOnly {
         } finally {
             System.setProperties(originalSystemProperties);
             processEnvironment.maybeSetEnvironment(originalEnv);
-            processEnvironment.maybeSetProcessDir(currentDir);
+            processEnvironment.maybeSetProcessDir(originalProcessDir);
         }
     }
-
 }
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
index 7381284..5dafad2 100644
--- 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
@@ -15,35 +15,22 @@
  */
 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.internal.concurrent.ExecutorFactory;
-import org.gradle.internal.concurrent.StoppableExecutor;
-import org.gradle.launcher.daemon.protocol.CloseInput;
 import org.gradle.launcher.daemon.protocol.ForwardInput;
-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.
+ * 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();
@@ -51,73 +38,43 @@ public class ForwardClientInput implements DaemonCommandAction {
         try {
             replacementStdin = new PipedInputStream(inputSource);
         } catch (IOException e) {
-            throw new GradleException("unable to wire client stdin to daemon stdin", e);
+            throw UncheckedException.throwAsUncheckedException(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);
+        execution.getConnection().onStdin(new StdinHandler() {
+            public void onInput(ForwardInput input) {
+                LOGGER.debug("Writing forwarded input on daemon's stdin.");
+                try {
+                    inputSource.write(input.getBytes());
+                } catch (IOException e) {
+                    LOGGER.warn("Received exception trying to forward client input.", e);
                 }
             }
-        };
-
-        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.
+            public void onEndOfInput() {
+                LOGGER.info("Closing daemon's stdin at end of input.");
                 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);
+                    inputSource.close();
+                } catch (IOException e) {
+                    LOGGER.warn("Problem closing output stream connected to replacement stdin", e);
                 } finally {
-                    inputReceiver.stop();
-                    LOGGER.debug("The input receiver has been stopped.");
+                    LOGGER.info("The daemon will no longer process any standard input.");
                 }
             }
         });
 
         try {
-            new StdinSwapper().swap(replacementStdin, new Callable<Void>() {
-                public Void call() {
-                    execution.proceed();
-                    return null;
-                }
-            });
-            replacementStdin.close();
+            try {
+                new StdinSwapper().swap(replacementStdin, new Callable<Void>() {
+                    public Void call() {
+                        execution.proceed();
+                        return null;
+                    }
+                });
+            } finally {
+                execution.getConnection().onStdin(null);
+                replacementStdin.close();
+            }
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
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
deleted file mode 100644
index c35c70c..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleClientDisconnectBeforeSendingCommand.java
+++ /dev/null
@@ -1,24 +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.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
index 2c912f8..9434c8b 100644
--- 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
@@ -16,6 +16,7 @@
 package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.launcher.daemon.protocol.Stop;
+import org.gradle.launcher.daemon.protocol.Success;
 
 /**
  * If the command is a Stop, asks the daemon to stop asynchronously and does not proceed with execution.
@@ -25,11 +26,12 @@ public class HandleStop implements DaemonCommandAction {
         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 
+                JVM to exit with code 1 (which is what we want) because the call to requestStopOnIdleTimeout() 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.getDaemonStateControl().requestStop();
+            execution.getDaemonStateControl().requestForcefulStop();
+            execution.getConnection().completed(new Success(null));
         } else {
             execution.proceed();
         }
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
index c4b6795..4ba0a4b 100644
--- 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
@@ -21,7 +21,7 @@ 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.LoggingOutputInternal;
 import org.gradle.logging.internal.OutputEvent;
 import org.gradle.logging.internal.OutputEventListener;
 
@@ -29,11 +29,11 @@ class LogToClient extends BuildCommandOnly {
 
     private static final Logger LOGGER = Logging.getLogger(LogToClient.class);
 
-    private final LoggingManagerInternal loggingManager;
+    private final LoggingOutputInternal loggingOutput;
     private final DaemonDiagnostics diagnostics;
 
-    public LogToClient(LoggingManagerInternal loggingManager, DaemonDiagnostics diagnostics) {
-        this.loggingManager = loggingManager;
+    public LogToClient(LoggingOutputInternal loggingOutput, DaemonDiagnostics diagnostics) {
+        this.loggingOutput = loggingOutput;
         this.diagnostics = diagnostics;
     }
 
@@ -43,7 +43,7 @@ class LogToClient extends BuildCommandOnly {
             public void onOutput(OutputEvent event) {
                 try {
                     if (event.getLogLevel().compareTo(buildLogLevel) >= 0) {
-                        execution.getConnection().dispatch(event);
+                        execution.getConnection().logEvent(event);
                     }
                 } catch (Exception e) {
                     //Ignore. It means the client has disconnected so no point sending him any log output.
@@ -53,13 +53,13 @@ class LogToClient extends BuildCommandOnly {
         };
 
         LOGGER.info(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS);
-        loggingManager.addOutputEventListener(listener);
+        loggingOutput.addOutputEventListener(listener);
         LOGGER.info(DaemonMessages.STARTED_RELAYING_LOGS + diagnostics.getPid() + "). The daemon log file: " + diagnostics.getDaemonLog());
 
         try {
             execution.proceed();
         } finally {
-            loggingManager.removeOutputEventListener(listener);
+            loggingOutput.removeOutputEventListener(listener);
         }
     } 
 }
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
index afd2ca4..e311dd0 100644
--- 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
@@ -42,7 +42,7 @@ public class ReturnResult implements DaemonCommandAction {
         }
 
         LOGGER.debug("Daemon is dispatching the build result: {}", result);
-        execution.getConnection().dispatch(result);
+        execution.getConnection().completed(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
index 0ed95e8..eee36f6 100644
--- 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
@@ -20,10 +20,10 @@ 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.protocol.DaemonUnavailable;
 
 /**
- * Updates the daemon idle/busy status, sending a DaemonBusy result back to the client if the daemon is busy.
+ * Updates the daemon idle/busy status, sending a DaemonUnavailable result back to the client if the daemon is busy.
  */
 public class StartBuildOrRespondWithBusy extends BuildCommandOnly {
     
@@ -34,24 +34,22 @@ public class StartBuildOrRespondWithBusy extends BuildCommandOnly {
         this.diagnostics = diagnostics;
     }
 
-    protected void doBuild(DaemonCommandExecution execution, Build build) {
+    protected void doBuild(final DaemonCommandExecution execution, final Build build) {
         DaemonStateControl stateCoordinator = execution.getDaemonStateControl();
 
-        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();
-            }
+        try {
+            Runnable command = new Runnable() {
+                public void run() {
+                    LOGGER.info("Daemon is about to start building: " + build + ". Dispatching build started information...");
+                    execution.getConnection().buildStarted(new BuildStarted(diagnostics));
+                    execution.proceed();
+                }
+            };
+
+            stateCoordinator.runCommand(command, execution.toString(), execution.getCommandAbandonedHandler());
+        } catch (DaemonUnavailableException e) {
+            LOGGER.info("Daemon will not handle the request: {} because is unavailable: {}", build, e.getMessage());
+            execution.getConnection().daemonUnavailable(new DaemonUnavailable(e.getMessage()));
         }
     }
-    
-    
-
 }
\ 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
index ba64ca6..b7c2c5b 100644
--- 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
@@ -22,17 +22,14 @@ import org.gradle.launcher.daemon.protocol.BuildAndStop;
 
 public class StartStopIfBuildAndStop implements DaemonCommandAction {
 
-    private static final Logger LOGGER = Logging.getLogger(StopConnectionAfterExecution.class);
+    private static final Logger LOGGER = Logging.getLogger(StartStopIfBuildAndStop.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.getDaemonStateControl().stopAsSoonAsIdle();
+            // Does not take effect until after execution has completed
+            execution.getDaemonStateControl().requestStop();
         }
+        execution.proceed();
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StdinHandler.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StdinHandler.java
new file mode 100644
index 0000000..2151378
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StdinHandler.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.launcher.daemon.server.exec;
+
+import org.gradle.launcher.daemon.protocol.ForwardInput;
+
+public interface StdinHandler {
+    void onInput(ForwardInput input);
+
+    void onEndOfInput();
+}
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
deleted file mode 100644
index 06f31f5..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopConnectionAfterExecution.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.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
index 6835aa7..1f8fb1a 100644
--- 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
@@ -17,36 +17,32 @@ 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() {
+        execution.getConnection().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 
+                    JVM to exit with code 1 (which is what we want) because the call to requestStopOnIdleTimeout() in
                     DaemonMain#doAction will throw a DaemonStoppedException. Note that at this point we will also 
                     immediately remove the daemon from the registry.
                 */
-                execution.getDaemonStateControl().requestStop();
+                execution.getDaemonStateControl().requestForcefulStop();
             }
         });
 
         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);
+            // Remove the handler
+            execution.getConnection().onDisconnect(null);
         }
     }
 
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 00f294b..420f89e 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
@@ -25,10 +25,11 @@ import org.gradle.initialization.GradleLauncherAction;
 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 org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 
 import java.io.File;
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.List;
 
 class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, InitializationAware, Serializable {
@@ -47,7 +48,7 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
         this.projectDirectory = parameters.getProjectDir();
         this.searchUpwards = parameters.isSearchUpwards();
         this.buildLogLevel = parameters.getBuildLogLevel();
-        this.arguments = parameters.getArguments();
+        this.arguments = parameters.getArguments(Collections.<String>emptyList());
         this.tasks = parameters.getTasks();
         this.action = action;
     }
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 3012e5f..47f2de6 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
@@ -23,7 +23,7 @@ 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.provider.input.ProviderOperationParameters;
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 
 import java.io.File;
 
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 fd6be80..449bd8c 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
@@ -27,11 +27,11 @@ import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEventRenderer;
-import org.gradle.logging.internal.logback.SimpleLogbackLoggingConfigurer;
+import org.gradle.process.internal.streams.SafeStreams;
 import org.gradle.tooling.internal.build.DefaultBuildEnvironment;
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
 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.tooling.internal.provider.connection.*;
 import org.gradle.util.GUtil;
 import org.gradle.util.GradleVersion;
 import org.slf4j.Logger;
@@ -40,10 +40,10 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.util.List;
 
-public class DefaultConnection implements InternalConnection {
+public class DefaultConnection implements InternalConnection, BuildActionRunner, ConfigurableConnection {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnection.class);
     private final EmbeddedExecuterSupport embeddedExecuterSupport;
-    private final SimpleLogbackLoggingConfigurer loggingConfigurer = new SimpleLogbackLoggingConfigurer();
+    private final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter();
 
     public DefaultConnection() {
         LOGGER.debug("Provider implementation created.");
@@ -53,10 +53,17 @@ public class DefaultConnection implements InternalConnection {
         LOGGER.debug("Embedded executer support created.");
     }
 
+    public void configure(ConnectionParameters parameters) {
+        ProviderConnectionParameters providerConnectionParameters = new ProtocolToModelAdapter().adapt(ProviderConnectionParameters.class, parameters);
+        configureLogging(providerConnectionParameters.getVerboseLogging());
+    }
+
     public void configureLogging(boolean verboseLogging) {
         LogLevel providerLogLevel = verboseLogging? LogLevel.DEBUG : LogLevel.INFO;
         LOGGER.debug("Configuring logging to level: {}", providerLogLevel);
-        loggingConfigurer.configure(providerLogLevel);
+        LoggingManagerInternal loggingManager = embeddedExecuterSupport.getLoggingServices().newInstance(LoggingManagerInternal.class);
+        loggingManager.setLevel(providerLogLevel);
+        loggingManager.start();
     }
 
     public ConnectionMetaDataVersion1 getMetaData() {
@@ -74,38 +81,56 @@ public class DefaultConnection implements InternalConnection {
     public void stop() {
     }
 
-    public void executeBuild(final BuildParametersVersion1 buildParameters,
-                             BuildOperationParametersVersion1 operationParameters) {
+    @Deprecated
+    public void executeBuild(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());
+        run(Void.class, new AdaptedOperationParameters(operationParameters, buildParameters.getTasks()));
     }
 
-    @Deprecated //getTheModel method has much convenient interface, e.g. avoids locking to building only models of a specific type
+    @Deprecated
     public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 parameters) {
-        return getTheModel(type, parameters);
+        logTargetVersion();
+        return run(type, new AdaptedOperationParameters(parameters));
     }
 
+    @Deprecated
     public <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 parameters) {
         logTargetVersion();
-        ProviderOperationParameters adaptedParameters = new AdaptedOperationParameters(parameters);
-        if (type == InternalBuildEnvironment.class) {
+        return run(type, new AdaptedOperationParameters(parameters));
+    }
 
-            //we don't really need to launch gradle to acquire information needed for BuildEnvironment
-            DaemonParameters daemonParameters = init(adaptedParameters);
+    public <T> BuildResult<T> run(Class<T> type, BuildParameters buildParameters) throws UnsupportedOperationException, IllegalStateException {
+        logTargetVersion();
+        ProviderOperationParameters providerParameters = adapter.adapt(ProviderOperationParameters.class, buildParameters, BuildLogLevelMixIn.class);
+        T result = run(type, providerParameters);
+        return new ProviderBuildResult<T>(result);
+    }
+
+    private <T> T run(Class<T> type, ProviderOperationParameters providerParameters) {
+        List<String> tasks = providerParameters.getTasks();
+        if (type.equals(Void.class) && tasks == null) {
+            throw new IllegalArgumentException("No model type or tasks specified.");
+        }
+        if (type == InternalBuildEnvironment.class) {
+            //we don't really need to launch the daemon to acquire information needed for BuildEnvironment
+            if (tasks != null) {
+                throw new IllegalArgumentException("Cannot run tasks and fetch the build environment model.");
+            }
+            DaemonParameters daemonParameters = init(providerParameters);
             DefaultBuildEnvironment out = new DefaultBuildEnvironment(
-                GradleVersion.current().getVersion(),
-                daemonParameters.getEffectiveJavaHome(),
-                daemonParameters.getEffectiveJvmArgs());
+                    GradleVersion.current().getVersion(),
+                    daemonParameters.getEffectiveJavaHome(),
+                    daemonParameters.getEffectiveJvmArgs());
 
             return type.cast(out);
         }
-        DelegatingBuildModelAction<T> action = new DelegatingBuildModelAction<T>(type);
-        return run(action, adaptedParameters);
+
+        DelegatingBuildModelAction<T> action = new DelegatingBuildModelAction<T>(type, tasks != null);
+        return run(action, providerParameters);
+    }
+
+    private void logTargetVersion() {
+        LOGGER.info("Tooling API uses target gradle version:" + " {}.", GradleVersion.current().getVersion());
     }
 
     private <T> T run(GradleLauncherAction<T> action, ProviderOperationParameters operationParameters) {
@@ -122,9 +147,9 @@ public class DefaultConnection implements InternalConnection {
             loggingServices = embeddedExecuterSupport.getLoggingServices();
             executer = embeddedExecuterSupport.getExecuter();
         } else {
-            loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
+            loggingServices = embeddedExecuterSupport.getLoggingServices().newLogging();
             loggingServices.get(OutputEventRenderer.class).configure(operationParameters.getBuildLogLevel());
-            DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParams, operationParameters.getStandardInput());
+            DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParams, operationParameters.getStandardInput(SafeStreams.emptyInput()));
             executer = clientServices.get(DaemonClient.class);
         }
         Factory<LoggingManagerInternal> loggingManagerFactory = loggingServices.getFactory(LoggingManagerInternal.class);
@@ -152,4 +177,5 @@ public class DefaultConnection implements InternalConnection {
         }
         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 f331e82..9cec903 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
@@ -28,9 +28,11 @@ import java.lang.reflect.InvocationTargetException;
 class DelegatingBuildModelAction<T> implements GradleLauncherAction<T>, Serializable {
     private transient GradleLauncherAction<T> action;
     private final Class<? extends T> type;
+    private final boolean runTasks;
 
-    public DelegatingBuildModelAction(Class<T> type) {
+    public DelegatingBuildModelAction(Class<T> type, boolean runTasks) {
         this.type = type;
+        this.runTasks = runTasks;
     }
 
     public T getResult() {
@@ -46,7 +48,7 @@ class DelegatingBuildModelAction<T> implements GradleLauncherAction<T>, Serializ
     private void loadAction(DefaultGradleLauncher launcher) {
         ClassLoaderRegistry classLoaderRegistry = launcher.getGradle().getServices().get(ClassLoaderRegistry.class);
         try {
-            action = (GradleLauncherAction<T>) classLoaderRegistry.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, Boolean.TYPE).newInstance(type, runTasks);
         } catch (InvocationTargetException e) {
             throw UncheckedException.unwrapAndRethrow(e);
         } catch (Exception e) {
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 30a9378..c05a704 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
@@ -21,7 +21,7 @@ import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.internal.*;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
+import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 
 /**
  * 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
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/AdaptedOperationParameters.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/AdaptedOperationParameters.java
new file mode 100644
index 0000000..4c9f31a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/AdaptedOperationParameters.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.provider.connection;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+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, null);
+    }
+
+    public AdaptedOperationParameters(BuildOperationParametersVersion1 delegate, List<String> tasks) {
+        this.delegate = delegate;
+        this.introspector = new CompatibleIntrospector(delegate);
+        this.tasks = tasks == null ? null : new LinkedList<String>(tasks);
+    }
+
+    public InputStream getStandardInput(InputStream defaultInput) {
+        return maybeGet(defaultInput, "getStandardInput");
+    }
+
+    public LogLevel getBuildLogLevel() {
+        return new BuildLogLevelMixIn(this).getBuildLogLevel();
+    }
+
+    public boolean getVerboseLogging(boolean defaultValue) {
+        return introspector.getSafely(defaultValue, "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(List<String> defaultArguments) {
+        return maybeGet(defaultArguments, "getArguments");
+    }
+    
+    public List<String> getTasks() {
+        return tasks;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java
new file mode 100644
index 0000000..eddfd98
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.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.tooling.internal.provider.connection;
+
+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 java.util.Collections;
+
+public class BuildLogLevelMixIn {
+    private final ProviderOperationParameters parameters;
+
+    public BuildLogLevelMixIn(ProviderOperationParameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public LogLevel getBuildLogLevel() {
+        LoggingCommandLineConverter converter = new LoggingCommandLineConverter();
+        CommandLineParser parser = new CommandLineParser().allowUnknownOptions();
+        converter.configure(parser);
+        ParsedCommandLine parsedCommandLine = parser.parse(parameters.getArguments(Collections.<String>emptyList()));
+        //configure verbosely only if arguments do not specify any log level.
+        if (parameters.getVerboseLogging(false) && !parsedCommandLine.hasAnyOption(converter.getLogLevelOptions())) {
+            return LogLevel.DEBUG;
+        }
+
+        LoggingConfiguration loggingConfiguration = converter.convert(parsedCommandLine);
+        return loggingConfiguration.getLogLevel();
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderBuildResult.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderBuildResult.java
new file mode 100644
index 0000000..ce4b3c8
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderBuildResult.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.tooling.internal.provider.connection;
+
+import org.gradle.tooling.internal.protocol.BuildResult;
+
+public class ProviderBuildResult<T> implements BuildResult<T> {
+    private final T result;
+
+    public ProviderBuildResult(T result) {
+        this.result = result;
+    }
+
+    public T getModel() {
+        return result;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderConnectionParameters.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderConnectionParameters.java
new file mode 100644
index 0000000..ff457fe
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderConnectionParameters.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.tooling.internal.provider.connection;
+
+public interface ProviderConnectionParameters {
+    boolean getVerboseLogging();
+
+    String getConsumerVersion();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderOperationParameters.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderOperationParameters.java
new file mode 100644
index 0000000..0650cde
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/ProviderOperationParameters.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.provider.connection;
+
+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 {
+    boolean getVerboseLogging(boolean defaultValue);
+
+    LogLevel getBuildLogLevel();
+
+    InputStream getStandardInput(InputStream defaultInput);
+
+    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();
+
+    TimeUnit getDaemonMaxIdleTimeUnits();
+
+    ProgressListenerVersion1 getProgressListener();
+
+    List<String> getArguments(List<String> defaultArguments);
+
+    List<String> getTasks();
+}
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
deleted file mode 100644
index b7fdb39..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/AdaptedOperationParameters.java
+++ /dev/null
@@ -1,150 +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.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
deleted file mode 100644
index aa36b67..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/ProviderOperationParameters.java
+++ /dev/null
@@ -1,66 +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.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/cli/BuildActionsFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
index c858a72..5c12558 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
@@ -21,16 +21,16 @@ 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.client.DaemonClient
+import org.gradle.launcher.daemon.client.SingleUseDaemonClient
 import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
+import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.logging.internal.OutputEventListener
 import org.gradle.util.SetSystemProperties
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.launcher.daemon.client.SingleUseDaemonClient
-import org.gradle.launcher.daemon.client.DaemonClient
-import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
 
 class BuildActionsFactoryTest extends Specification {
     @Rule
@@ -107,8 +107,8 @@ class BuildActionsFactoryTest extends Specification {
         def action = convert('--stop')
 
         then:
-        action instanceof ActionAdapter
-        action.action instanceof StopDaemonAction
+        // Relying on impl of Actions.toAction(Runnable)
+        action.runnable instanceof StopDaemonAction
     }
 
     def runsDaemonInForeground() {
@@ -116,8 +116,8 @@ class BuildActionsFactoryTest extends Specification {
         def action = convert('--foreground')
 
         then:
-        action instanceof ActionAdapter
-        action.action instanceof DaemonMain
+        // Relying on impl of Actions.toAction(Runnable)
+        action.runnable instanceof DaemonMain
     }
 
     def executesBuildWithSingleUseDaemonIfJavaHomeIsNotCurrent() {
@@ -189,20 +189,20 @@ class BuildActionsFactoryTest extends Specification {
     }
 
     void isDaemon(def action) {
-        assert action instanceof ActionAdapter
-        assert action.action instanceof RunBuildAction
-        assert action.action.executer instanceof DaemonClient
+        // Relying on impl of Actions.toAction(Runnable)
+        assert action.runnable instanceof RunBuildAction
+        assert action.runnable.executer instanceof DaemonClient
     }
 
     void isInProcess(def action) {
-        assert action instanceof ActionAdapter
-        assert action.action instanceof RunBuildAction
-        assert action.action.executer instanceof InProcessGradleLauncherActionExecuter
+        // Relying on impl of Actions.toAction(Runnable)
+        assert action.runnable instanceof RunBuildAction
+        assert action.runnable.executer instanceof InProcessGradleLauncherActionExecuter
     }
 
     void isSingleUseDaemon(def action) {
-        assert action instanceof ActionAdapter
-        assert action.action instanceof RunBuildAction
-        assert action.action.executer instanceof SingleUseDaemonClient
+        // Relying on impl of Actions.toAction(Runnable)
+        assert action.runnable instanceof RunBuildAction
+        assert action.runnable.executer instanceof SingleUseDaemonClient
     }
 }
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
index 322bc61..62b70b6 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/GuiActionsFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/GuiActionsFactoryTest.groovy
@@ -16,8 +16,8 @@
 
 package org.gradle.launcher.cli
 
-import spock.lang.Specification
 import org.gradle.cli.CommandLineParser
+import spock.lang.Specification
 
 class GuiActionsFactoryTest extends Specification {
     final GuiActionsFactory factory = new GuiActionsFactory()
@@ -30,8 +30,8 @@ class GuiActionsFactoryTest extends Specification {
         def action = factory.createAction(parser, cl)
 
         then:
-        action instanceof ActionAdapter
-        action.action instanceof GuiActionsFactory.ShowGuiAction
+        // Relying on impl of Actions.toAction(Runnable)
+        action.runnable instanceof GuiActionsFactory.ShowGuiAction
     }
 
 }
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
index 228a6f1..236c56a 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.launcher.daemon
 
+import org.gradle.api.logging.LogLevel
 import org.gradle.configuration.GradleLauncherMetaData
 import org.gradle.launcher.daemon.client.DaemonClient
 import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
@@ -24,7 +25,6 @@ import org.gradle.tooling.internal.provider.ExecuteBuildAction
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.api.logging.LogLevel
 
 /**
  * Exercises the basic mechanics using an embedded daemon.
@@ -37,7 +37,7 @@ 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'],
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientConnectionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientConnectionTest.groovy
new file mode 100644
index 0000000..75c177f
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientConnectionTest.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.launcher.daemon.client
+
+import org.gradle.api.GradleException
+import org.gradle.messaging.remote.internal.Connection
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 8/31/12
+ */
+class DaemonClientConnectionTest extends Specification {
+
+    final delegate = Mock(Connection)
+    final onFailure = Mock(Runnable)
+    final connection = new DaemonClientConnection(delegate, 'id', onFailure)
+
+    def "stops"() {
+        when:
+        connection.stop()
+        then:
+        1 * delegate.stop()
+        0 * onFailure.run()
+
+        when:
+        connection.requestStop()
+        then:
+        1 * delegate.requestStop()
+        0 * onFailure.run()
+    }
+
+    def "dispatches messages"() {
+        when:
+        connection.dispatch("foo")
+
+        then:
+        1 * delegate.dispatch("foo")
+        0 * onFailure.run()
+    }
+
+    def "receives messages"() {
+        given:
+        delegate.receive() >> "bar"
+
+        when:
+        def out = connection.receive()
+
+        then:
+        "bar" == out
+        0 * onFailure.run()
+    }
+
+    def "handles failed dispatch"() {
+        given:
+        delegate.dispatch("foo") >> { throw new FooException() }
+
+        when:
+        connection.dispatch("foo")
+
+        then:
+        def ex = thrown(GradleException)
+        ex.cause instanceof FooException
+        1 * onFailure.run()
+    }
+
+    def "handles failed receive"() {
+        given:
+        delegate.receive() >> { throw new FooException() }
+
+        when:
+        connection.receive()
+
+        then:
+        def ex = thrown(GradleException)
+        ex.cause instanceof FooException
+        1 * onFailure.run()
+    }
+
+    class FooException extends RuntimeException {}
+}
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
index 829c179..4866444 100644
--- 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
@@ -35,7 +35,7 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
 
     def received = new LinkedBlockingQueue()
     def dispatch = { received << it } as Dispatch
-    
+
     def receivedCommand() {
         received.poll(5, TimeUnit.SECONDS)
     }
@@ -50,22 +50,22 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
     boolean receiveClosed() {
         receivedCommand() instanceof CloseInput
     }
-    
+
     def forwarder
 
     def createForwarder() {
-        forwarder = new DaemonClientInputForwarder(inputStream, dispatch, executorFactory, {12} as IdGenerator, bufferSize)
+        forwarder = new DaemonClientInputForwarder(inputStream, dispatch, executorFactory, { 12 } as IdGenerator, 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")
@@ -81,7 +81,7 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
         then:
         receiveClosed()
     }
-    
+
     def "close input is sent when the underlying input stream is closed"() {
         when:
         source << toPlatformLineSeparators("abc\ndef\n")
@@ -93,16 +93,22 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
 
         and:
         receiveClosed()
+
+        when:
+        forwarder.stop()
+
+        then:
+        !receivedCommand()
     }
-        
+
     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"
@@ -129,7 +135,7 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
         and:
         receiveClosed()
     }
-    
+
     def cleanup() {
         source.close()
         inputStream.close()
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
index a9bc431..2536cd7 100644
--- 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
@@ -20,33 +20,28 @@ import org.gradle.internal.id.IdGenerator
 import org.gradle.launcher.daemon.context.DaemonCompatibilitySpec
 import org.gradle.launcher.exec.BuildActionParameters
 import org.gradle.logging.internal.OutputEventListener
-import org.gradle.messaging.remote.internal.Connection
 import org.gradle.util.ConcurrentSpecification
 import org.gradle.launcher.daemon.protocol.*
 
 class DaemonClientTest extends ConcurrentSpecification {
     final DaemonConnector connector = Mock()
-    final DaemonConnection daemonConnection = Mock()
-    final Connection<Object> connection = Mock()
+    final DaemonClientConnection connection = Mock()
     final OutputEventListener outputEventListener = Mock()
     final DaemonCompatibilitySpec compatibilitySpec = Mock()
     final IdGenerator<?> idGenerator = {12} as IdGenerator
     final DaemonClient client = new DaemonClient(connector, outputEventListener, compatibilitySpec, new ByteArrayInputStream(new byte[0]), executorFactory, idGenerator)
 
-    def setup() {
-        daemonConnection.getConnection() >> connection
-    }
-
     def stopsTheDaemonWhenRunning() {
         when:
         client.stop()
 
         then:
-        2 * connector.maybeConnect(compatibilitySpec) >>> [daemonConnection, null]
+        _ * connection.uid >> '1'
+        2 * connector.maybeConnect(compatibilitySpec) >>> [connection, null]
         1 * connection.dispatch({it instanceof Stop})
         1 * connection.receive() >> new Success(null)
+        1 * connection.dispatch({it instanceof Finished})
         1 * connection.stop()
-        daemonConnection.getConnection() >> connection // why do I need this? Why doesn't the interaction specified in setup cover me?
         0 * _
     }
 
@@ -60,80 +55,125 @@ class DaemonClientTest extends ConcurrentSpecification {
     }
 
     def "stops all compatible daemons"() {
+        DaemonClientConnection connection2 = Mock()
+
         when:
         client.stop()
 
         then:
-        3 * connector.maybeConnect(compatibilitySpec) >>> [daemonConnection, daemonConnection, null]
-        2 * connection.dispatch({it instanceof Stop})
-        2 * connection.receive() >> new Success(null)
+        _ * connection.uid >> '1'
+        _ * connection2.uid >> '2'
+        3 * connector.maybeConnect(compatibilitySpec) >>> [connection, connection2, null]
+        1 * connection.dispatch({it instanceof Stop})
+        1 * connection.receive() >> new Success(null)
+        1 * connection.dispatch({it instanceof Finished})
+        1 * connection.stop()
+        1 * connection2.dispatch({it instanceof Stop})
+        1 * connection2.receive() >> new Success(null)
+        1 * connection2.dispatch({it instanceof Finished})
+        1 * connection2.stop()
+        0 * _
+    }
+
+    def "stops each connection at most once"() {
+        when:
+        client.stop()
+
+        then:
+        _ * connection.uid >> '1'
+        3 * connector.maybeConnect(compatibilitySpec) >>> [connection, connection, null]
+        1 * connection.dispatch({it instanceof Stop})
+        1 * connection.receive() >> new Success(null)
+        1 * connection.dispatch({it instanceof Finished})
+        2 * connection.stop()
+        0 * _
     }
 
     def executesAction() {
         when:
-        def result = client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        def result = client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
 
         then:
         result == '[result]'
-        1 * connector.connect(compatibilitySpec) >> daemonConnection
+        1 * connector.connect(compatibilitySpec) >> connection
         1 * connection.dispatch({it instanceof Build})
-        2 * connection.receive() >>> [Mock(BuildStarted), new Success('[result]')]
+        2 * connection.receive() >>> [Stub(BuildStarted), new Success('[result]')]
+        1 * connection.dispatch({it instanceof CloseInput})
+        1 * connection.dispatch({it instanceof Finished})
         1 * connection.stop()
+        0 * _
     }
 
     def rethrowsFailureToExecuteAction() {
-        GradleLauncherAction<String> action = Mock()
-        BuildActionParameters parameters = Mock()
         RuntimeException failure = new RuntimeException()
 
         when:
-        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
 
         then:
         RuntimeException e = thrown()
         e == failure
-        1 * connector.connect(compatibilitySpec) >> daemonConnection
+        1 * connector.connect(compatibilitySpec) >> connection
         1 * connection.dispatch({it instanceof Build})
-        2 * connection.receive() >>> [Mock(BuildStarted), new CommandFailure(failure)]
+        2 * connection.receive() >>> [Stub(BuildStarted), new CommandFailure(failure)]
+        1 * connection.dispatch({it instanceof CloseInput})
+        1 * connection.dispatch({it instanceof Finished})
         1 * connection.stop()
+        0 * _
     }
     
     def "tries to find a different daemon if getting the first result from the daemon fails"() {
+        DaemonClientConnection connection2 = Mock()
+
         when:
-        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
 
         then:
-        2 * connector.connect(compatibilitySpec) >> daemonConnection
-        connection.dispatch({it instanceof Build}) >> { throw new RuntimeException("Boo!")} >> { /* success */ }
-        2 * connection.receive() >>> [Mock(BuildStarted), new Success('')]
+        2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
+        1 * connection.dispatch({it instanceof Build}) >> { throw new RuntimeException("Boo!")}
+        1 * connection.stop()
+        2 * connection2.receive() >>> [Stub(BuildStarted), new Success('')]
+        0 * connection._
     }
 
     def "tries to find a different daemon if the daemon is busy"() {
+        DaemonClientConnection connection2 = Mock()
+
         when:
-        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
 
         then:
-        2 * connector.connect(compatibilitySpec) >> daemonConnection
-        connection.receive() >>> [Mock(DaemonBusy), Mock(BuildStarted), new Success('')]
+        2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
+        1 * connection.dispatch({it instanceof Build})
+        1 * connection.receive() >> Stub(DaemonUnavailable)
+        1 * connection.dispatch({it instanceof Finished})
+        1 * connection.stop()
+        2 * connection2.receive() >>> [Stub(BuildStarted), new Success('')]
+        0 * connection._
     }
 
     def "tries to find a different daemon if the first result is null"() {
+        DaemonClientConnection connection2 = Mock()
+
         when:
-        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        client.execute(Stub(GradleLauncherAction), Stub(BuildActionParameters))
 
         then:
-        3 * connector.connect(compatibilitySpec) >> daemonConnection
-        //first busy, then null, then build started...
-        connection.receive() >>> [Mock(DaemonBusy), null, Mock(BuildStarted), new Success('')]
+        2 * connector.connect(compatibilitySpec) >>> [connection, connection2]
+        1 * connection.dispatch({it instanceof Build})
+        1 * connection.receive() >> null
+        1 * connection.stop()
+        2 * connection2.receive() >>> [Stub(BuildStarted), new Success('')]
+        0 * connection._
     }
 
     def "does not loop forever finding usable daemons"() {
         given:
-        connector.connect(compatibilitySpec) >> daemonConnection
-        connection.receive() >> Mock(DaemonBusy)
-        
+        connector.connect(compatibilitySpec) >> connection
+        connection.receive() >> Mock(DaemonUnavailable)
+
         when:
-        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+        client.execute(Stub(GradleLauncherAction), Stub(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
index 1db55c6..957388f 100644
--- 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
@@ -66,7 +66,7 @@ class DefaultDaemonConnectorTest extends Specification {
         def daemonNum = daemonCounter++
         DaemonContext context = new DefaultDaemonContext(daemonNum.toString(), javaHome, javaHome, daemonNum, 1000, [])
         def address = createAddress(daemonNum)
-        registry.store(address, context, "password")
+        registry.store(address, context, "password", false)
         registry.markBusy(address)
         return new DaemonStartupInfo(daemonNum.toString(), null);
     }
@@ -75,7 +75,7 @@ class DefaultDaemonConnectorTest extends Specification {
         def daemonNum = daemonCounter++
         DaemonContext context = new DefaultDaemonContext(daemonNum.toString(), javaHome, javaHome, daemonNum, 1000, [])
         def address = createAddress(daemonNum)
-        registry.store(address, context, "password")
+        registry.store(address, context, "password", true)
     }
 
     def theConnector
@@ -111,6 +111,18 @@ class DefaultDaemonConnectorTest extends Specification {
         connection && connection.connection.num < 12
     }
 
+    def "created connection removes from registry on failure"() {
+        given:
+        startIdleDaemon()
+
+        when:
+        def connection = connector.maybeConnect( { true } as ExplainingSpec)
+        connection.onFailure.run()
+
+        then:
+        registry.remove( _ as Address )
+    }
+
     def "maybeConnect() returns null when no daemon matches spec"() {
         given:
         startIdleDaemon()
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
index c001742..7a774f0 100644
--- 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
@@ -43,7 +43,7 @@ class DaemonRegistryServicesTest extends Specification {
         5.times { idx ->
             concurrent.start {
                 def context = new DefaultDaemonContext("$idx", new File("$idx"), new File("$idx"), idx, 5000, [])
-                registry.store(new SocketInetAddress(new Inet6Address(), 8888 + idx), context, "foo-$idx")
+                registry.store(new SocketInetAddress(new Inet6Address(), 8888 + idx), context, "foo-$idx", true)
             }
         }
         concurrent.finished()
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
index 31e64cc..0e7be47 100644
--- 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
@@ -30,9 +30,12 @@ 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)
+    final updater = new DomainRegistryUpdater(registry, context, "password")
 
     def "marks idle"() {
+        given:
+        updater.onStart(address)
+
         when:
         updater.onCompleteActivity()
 
@@ -42,7 +45,8 @@ public class DomainRegistryUpdaterTest extends Specification {
 
     def "ignores empty cache on marking idle"() {
         given:
-        1 * registry.markIdle(address) >> { throw new EmptyRegistryException("") }
+        updater.onStart(address)
+        registry.markIdle(address) >> { throw new EmptyRegistryException("") }
 
         when:
         updater.onCompleteActivity()
@@ -52,6 +56,9 @@ public class DomainRegistryUpdaterTest extends Specification {
     }
 
     def "marks busy"() {
+        given:
+        updater.onStart(address)
+
         when:
         updater.onStartActivity()
 
@@ -61,7 +68,8 @@ public class DomainRegistryUpdaterTest extends Specification {
 
     def "ignores empty cache on marking busy"() {
         given:
-        1 * registry.markBusy(address) >> { throw new EmptyRegistryException("") }
+        updater.onStart(address)
+        registry.markBusy(address) >> { throw new EmptyRegistryException("") }
 
         when:
         updater.onStartActivity()
@@ -70,12 +78,13 @@ public class DomainRegistryUpdaterTest extends Specification {
         noExceptionThrown()
     }
 
-     def "ignores empty cache on stopping"() {
+    def "ignores empty cache on stopping"() {
         given:
-        1 * registry.remove(address) >> { throw new EmptyRegistryException("") }
+        updater.onStart(address)
+        registry.remove(address) >> { throw new EmptyRegistryException("") }
 
         when:
-        updater.onStop()
+        updater.stop()
 
         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
index 507ff79..dd5636b 100644
--- 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
@@ -38,8 +38,8 @@ class EmbeddedDaemonRegistrySpec extends Specification {
 
     def "lifecycle"() {
         given:
-        store(address(10), context, "password")
-        store(address(20), context, "password")
+        store(address(10), context, "password", true)
+        store(address(20), context, "password", true)
 
         expect:
         all.size() == 2
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
index d243108..d4305c8 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
@@ -17,13 +17,13 @@
 package org.gradle.launcher.daemon.registry
 
 import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.launcher.daemon.context.DaemonContext
 import org.gradle.launcher.daemon.context.DaemonContextBuilder
 import org.gradle.messaging.remote.Address
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+
 import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.createDefaultFileLockManager
 import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockUncleanly
 
@@ -32,15 +32,15 @@ class PersistentDaemonRegistryTest extends Specification {
     @Rule TemporaryFolder tmp
     
     int addressCounter = 0
+    def lockManager = createDefaultFileLockManager()
 
     def "corrupt registry file is ignored"() {
         given:
         def file = tmp.file("registry")
-        def lockManager = createDefaultFileLockManager()
         def registry = new PersistentDaemonRegistry(file, lockManager)
         
         and:
-        registry.store(address(), daemonContext(), "password")
+        registry.store(address(), daemonContext(), "password", true)
 
         expect:
         registry.all.size() == 1
@@ -51,9 +51,39 @@ class PersistentDaemonRegistryTest extends Specification {
         then:
         registry.all.empty
     }
+
+    def "safely removes from registry file"() {
+        given:
+        def registry = new PersistentDaemonRegistry(tmp.file("registry"), lockManager)
+        def address = address()
+
+        and:
+        registry.store(address, daemonContext(), "password", true)
+
+        when:
+        registry.remove(address)
+
+        then:
+        registry.all.empty
+
+        and: //it is safe to remove it again
+        registry.remove(address)
+    }
+
+    def "safely removes if registry empty"() {
+        given:
+        def registry = new PersistentDaemonRegistry(tmp.file("registry"), lockManager)
+        def address = address()
+
+        when:
+        registry.remove(address)
+
+        then:
+        registry.all.empty
+    }
     
     DaemonContext daemonContext() {
-        new DaemonContextBuilder(new NativeServices().get(ProcessEnvironment)).with {
+        new DaemonContextBuilder([maybeGetPid: {null}] as ProcessEnvironment).with {
             daemonRegistryDir = tmp.createDir("daemons")
             create()
         }
@@ -71,6 +101,13 @@ class PersistentDaemonRegistryTest extends Specification {
             this.displayName = displayName
         }
 
+        boolean equals(o) {
+            displayName == o.displayName
+        }
+
+        int hashCode() {
+            displayName.hashCode()
+        }
     }
 
 }
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
index fac9b84..0b862db 100644
--- 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
@@ -31,7 +31,6 @@ 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.internal.concurrent.ExecutorFactory
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
@@ -74,7 +73,7 @@ class DaemonServerExceptionHandlingTest extends Specification {
         //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),
+                return new DefaultDaemonCommandExecuter(new DefaultGradleLauncherFactory(loggingServices),
                         get(ProcessEnvironment), loggingServices.getFactory(LoggingManagerInternal.class).create(), new File("dummy")) {
                     List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
                         def actions = new LinkedList(super.createActions(daemonContext));
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
index 496e2f5..1c92c0d 100644
--- 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
@@ -15,111 +15,432 @@
  */
 package org.gradle.launcher.daemon.server
 
-import java.util.concurrent.locks.Condition
+import org.gradle.launcher.daemon.server.exec.DaemonUnavailableException
 import spock.lang.Specification
-import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution
 
+import java.util.concurrent.TimeUnit
 /**
  * by Szczepan Faber, created at: 2/6/12
  */
 class DaemonStateCoordinatorTest extends Specification {
+    final Runnable onStartCommand = Mock(Runnable)
+    final Runnable onFinishCommand = Mock(Runnable)
+    final Runnable onDisconnect = Mock(Runnable)
+    final coordinator = new DaemonStateCoordinator(onStartCommand, onFinishCommand)
 
-    def coordinator = new DaemonStateCoordinator(Mock(Runnable), Mock(Runnable), Mock(Runnable), Mock(Runnable), Mock(Runnable))
-
-    def "requesting stop lifecycle"() {
-        coordinator.asyncStop = Mock(Runnable)
-
+    def "can stop multiple times"() {
         expect:
         !coordinator.stopped
 
-        when: "requested first time"
-        def passOne = coordinator.requestStop()
+        when: "stopped first time"
+        coordinator.stop()
 
-        then: "retruns true and schedules stopping"
-        passOne == true
-        1 * coordinator.asyncStop.run()
-        1 * coordinator.onStopRequested.run()
-        coordinator.stoppingOrStopped
-        !coordinator.stopped
+        then: "stops"
+        coordinator.stopped
 
         when: "requested again"
-        def passTwo = coordinator.requestStop()
+        coordinator.stop()
 
-        then: "only returns false"
-        passTwo == false
-        0 * coordinator.asyncStop.run()
-        0 * coordinator.onStopRequested.run()
-        coordinator.stoppingOrStopped
-        !coordinator.stopped
+        then:
+        coordinator.stopped
+        0 * _._
     }
 
-    def "stopping lifecycle"() {
-        coordinator.condition = Mock(Condition)
+    def "await idle timeout throws exception when already stopped"() {
+        given:
+        coordinator.stop()
 
-        expect:
-        !coordinator.stopped
+        when:
+        coordinator.stopOnIdleTimeout(10000, TimeUnit.SECONDS)
 
-        when: "stopped first time"
-        coordinator.stop()
+        then:
+        DaemonStoppedException e = thrown()
+    }
 
-        then: "stops"
-        1 * coordinator.onStop.run()
-        1 * coordinator.onStopRequested.run()
-        1 * coordinator.condition.signalAll()
+    def "await idle timeout waits for specified time and then stops"() {
+        when:
+        coordinator.stopOnIdleTimeout(100, TimeUnit.MILLISECONDS)
+
+        then:
         coordinator.stopped
+        0 * _._
+    }
 
-        when: "requested again"
-        coordinator.stop()
+    def "runs actions when command is run"() {
+        Runnable command = Mock()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
 
         then:
-        0 * coordinator.onStopRequested.run()
-        1 * coordinator.onStop.run()
-        1 * coordinator.condition.signalAll()
-        coordinator.stopped
+        1 * onStartCommand.run()
+        1 * command.run()
+        1 * onFinishCommand.run()
+        0 * _._
+    }
+
+    def "runs actions when command fails"() {
+        Runnable command = Mock()
+        def failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * onStartCommand.run()
+        1 * command.run() >> { throw failure }
+        1 * onFinishCommand.run()
+        0 * _._
+    }
+
+    def "cannot run command when another command is running"() {
+        Runnable command = Mock()
+
+        given:
+        command.run() >> { coordinator.runCommand(Mock(Runnable), "other", Mock(Runnable)) }
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException e = thrown()
+        e.message == 'This daemon is currently executing: command'
     }
 
-    def "stopAsSoonAsIdle when idle"() {
+    def "cannot run command after stop requested"() {
+        Runnable command = Mock()
+
         given:
-        coordinator.start()
+        coordinator.requestStop()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException e = thrown()
+        e.message == 'This daemon has stopped.'
+    }
+
+    def "cannot run command after forceful stop requested"() {
+        Runnable command = Mock()
+
+        given:
+        coordinator.requestForcefulStop()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException e = thrown()
+        e.message == 'This daemon has stopped.'
+    }
+
+    def "cannot run command after stopped"() {
+        Runnable command = Mock()
+
+        given:
+        coordinator.requestStop()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException e = thrown()
+        e.message == 'This daemon has stopped.'
+    }
+
+    def "cannot run command after start command action fails"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        1 * onStartCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException unavailableException = thrown()
+        unavailableException.message == 'This daemon is in a broken state and will stop.'
+    }
+
+    def "cannot run command after finish command action has failed"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        1 * onStartCommand.run()
+        1 * command.run()
+        1 * onFinishCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        DaemonUnavailableException unavailableException = thrown()
+        unavailableException.message == 'This daemon is in a broken state and will stop.'
+    }
+
+    def "await idle time returns immediately after start command action has failed"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * onStartCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.stopOnIdleTimeout(10000, TimeUnit.SECONDS)
+
+        then:
+        IllegalStateException illegalStateException = thrown()
+        illegalStateException.message == 'This daemon is in a broken state.'
+    }
+
+    def "can stop when start command action has failed"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * onStartCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.stop()
+
+        then:
+        0 * _._
+    }
+
+    def "can stop when finish command action has failed"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * onStartCommand.run()
+        1 * command.run()
+        1 * onFinishCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.stop()
+
+        then:
+        0 * _._
+    }
+
+    def "await idle time returns immediately after finish command action has failed"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * onStartCommand.run()
+        1 * command.run()
+        1 * onFinishCommand.run() >> { throw failure }
+        0 * _._
+
+        when:
+        coordinator.stopOnIdleTimeout(10000, TimeUnit.SECONDS)
+
+        then:
+        IllegalStateException illegalStateException = thrown()
+        illegalStateException.message == 'This daemon is in a broken state.'
+    }
 
+    def "requestStop stops immediately when idle"() {
         expect:
         coordinator.idle
 
         when:
-        coordinator.stopAsSoonAsIdle()
+        coordinator.requestStop()
 
         then:
         coordinator.stopped
         coordinator.stoppingOrStopped
+    }
+
+    def "requestStop stops once current command has completed"() {
+        Runnable command = Mock()
+
+        when:
+        coordinator.runCommand(command, "some command", onDisconnect)
+
+        then:
+        1 * command.run() >> {
+            assert coordinator.busy
+            coordinator.requestStop()
+            assert !coordinator.stopped
+            assert coordinator.stoppingOrStopped
+        }
+
+        and:
+        coordinator.stopped
 
         and:
-        1 * coordinator.onStopRequested.run()
-        1 * coordinator.onStop.run()
+        1 * onStartCommand.run()
+        0 * _._
     }
 
-    def "stopAsSoonAsIdle when busy"() {
-        given:
-        coordinator.start()
-        coordinator.onStartCommand(Mock(DaemonCommandExecution))
+    def "requestStop stops when command fails"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
 
+        when:
+        coordinator.runCommand(command, "some command", onDisconnect)
+
+        then:
+        1 * command.run() >> {
+            assert coordinator.busy
+            coordinator.requestStop()
+            assert !coordinator.stopped
+            assert coordinator.stoppingOrStopped
+            throw failure
+        }
+
+        and:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
+        coordinator.stopped
+
+        and:
+        1 * onStartCommand.run()
+        0 * _._
+    }
+
+    def "await idle time returns after command has finished and stop requested"() {
+        Runnable command = Mock()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+        coordinator.stopOnIdleTimeout(10000, TimeUnit.SECONDS)
+
+        then:
+        DaemonStoppedException e = thrown()
+
+        and:
+        1 * onStartCommand.run()
+        1 * command.run() >> {
+            coordinator.requestStop()
+        }
+        0 * _._
+    }
+
+    def "requestForcefulStop stops immediately when idle"() {
         expect:
-        coordinator.busy
+        !coordinator.stopped
 
         when:
-        coordinator.stopAsSoonAsIdle()
+        coordinator.requestForcefulStop()
 
         then:
+        coordinator.stoppingOrStopped
+        coordinator.stopped
+        0 * _._
+    }
+
+    def "requestForcefulStop notifies disconnect handler and stops immediately when command running"() {
+        Runnable command = Mock()
+
+        expect:
         !coordinator.stopped
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+
+        then:
         coordinator.stoppingOrStopped
+        coordinator.stopped
+        1 * onStartCommand.run()
+        1 * command.run() >> {
+            assert !coordinator.stopped
+            coordinator.requestForcefulStop()
+            assert coordinator.stopped
+        }
+        1 * onDisconnect.run()
+        0 * _._
+    }
 
-        and:
-        1 * coordinator.onStopRequested.run()
+    def "requestForcefulStop stops after disconnect action fails"() {
+        Runnable command = Mock()
+        RuntimeException failure = new RuntimeException()
 
         when:
-        coordinator.onFinishCommand()
+        coordinator.runCommand(command, "command", onDisconnect)
 
         then:
+        RuntimeException e = thrown()
+        e == failure
+
+        and:
         coordinator.stopped
-        1 * coordinator.onStop.run()
+
+        and:
+        1 * onStartCommand.run()
+        1 * command.run() >> {
+            coordinator.requestForcefulStop()
+        }
+        1 * onDisconnect.run() >> {
+            throw failure
+        }
+        0 * _._
     }
+
+    def "await idle time returns immediately when forceful stop requested and command running"() {
+        Runnable command = Mock()
+
+        when:
+        coordinator.runCommand(command, "command", onDisconnect)
+        coordinator.stopOnIdleTimeout(10000, TimeUnit.SECONDS)
+
+        then:
+        DaemonStoppedException e = thrown()
+
+        and:
+        1 * onStartCommand.run()
+        1 * command.run() >> {
+            coordinator.requestForcefulStop()
+        }
+        1 * onDisconnect.run()
+        0 * _._
+    }
+
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DefaultDaemonConnectionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DefaultDaemonConnectionTest.groovy
new file mode 100644
index 0000000..388bb32
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DefaultDaemonConnectionTest.groovy
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.protocol.CloseInput
+import org.gradle.launcher.daemon.protocol.ForwardInput
+import org.gradle.launcher.daemon.server.exec.StdinHandler
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.util.ConcurrentSpecification
+
+import java.util.concurrent.CountDownLatch
+import org.gradle.launcher.daemon.protocol.Stop
+import java.util.concurrent.TimeUnit
+
+class DefaultDaemonConnectionTest extends ConcurrentSpecification {
+    final TestConnection connection = new TestConnection()
+    final DefaultDaemonConnection daemonConnection = new DefaultDaemonConnection(connection, executorFactory)
+
+    def cleanup() {
+        connection.disconnect()
+        daemonConnection.stop()
+    }
+
+    def "forwards queued input events to stdin handler until end of input received"() {
+        StdinHandler handler = Mock()
+        def input1 = new ForwardInput(1, "hello".bytes)
+        def input2 = new ForwardInput(2, "hello".bytes)
+        def closeInput = new CloseInput(3)
+        def received = new CountDownLatch(1)
+
+        when:
+        daemonConnection.onStdin(handler)
+        connection.queueIncoming(input1)
+        connection.queueIncoming(input2)
+        connection.queueIncoming(closeInput)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.onInput(input1)
+        1 * handler.onInput(input2)
+        1 * handler.onEndOfInput() >> { received.countDown() }
+        0 * handler._
+    }
+
+    def "generates end of stdin event when connection disconnects"() {
+        StdinHandler handler = Mock()
+        def input1 = new ForwardInput(1, "hello".bytes)
+        def received = new CountDownLatch(1)
+
+        when:
+        daemonConnection.onStdin(handler)
+        connection.queueIncoming(input1)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.onInput(input1) >> { received.countDown() }
+        1 * handler.onEndOfInput()
+        0 * handler._
+    }
+
+    def "generates end of stdin event when connection stopped"() {
+        StdinHandler handler = Mock()
+
+        when:
+        daemonConnection.onStdin(handler)
+        daemonConnection.stop()
+
+        then:
+        1 * handler.onEndOfInput()
+        0 * handler._
+    }
+
+    def "buffers stdin events"() {
+        StdinHandler handler = Mock()
+        def input1 = new ForwardInput(1, "hello".bytes)
+        def input2 = new ForwardInput(2, "hello".bytes)
+        def closeInput = new CloseInput(3)
+        def received = new CountDownLatch(1)
+
+        when:
+        connection.queueIncoming(input1)
+        connection.queueIncoming(input2)
+        connection.queueIncoming(closeInput)
+        daemonConnection.onStdin(handler)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.onInput(input1)
+        1 * handler.onInput(input2)
+        1 * handler.onEndOfInput() >> { received.countDown() }
+        0 * handler._
+    }
+
+    def "does not notify stdin handler once it is removed"() {
+        StdinHandler handler = Mock()
+
+        when:
+        daemonConnection.onStdin(handler)
+        daemonConnection.onStdin(null)
+        connection.disconnect()
+        daemonConnection.stop()
+
+        then:
+        0 * handler._
+    }
+
+    def "discards queued messages on stop"() {
+        when:
+        connection.queueIncoming("incoming")
+        connection.queueIncoming(new ForwardInput(1, "hello".bytes))
+        connection.disconnect()
+        daemonConnection.stop()
+
+        then:
+        notThrown()
+    }
+
+    def "handles case where cannot receive from connection"() {
+        when:
+        connection.queueBroken()
+        daemonConnection.stop()
+
+        then:
+        notThrown()
+    }
+
+    def "handles failure to notify stdin handler"() {
+        StdinHandler handler = Mock()
+        def input1 = new ForwardInput(1, "hello".bytes)
+        def input2 = new ForwardInput(2, "hello".bytes)
+        def closeInput = new CloseInput(3)
+        def received = new CountDownLatch(1)
+
+        when:
+        connection.queueIncoming(input1)
+        connection.queueIncoming(input2)
+        connection.queueIncoming(closeInput)
+        daemonConnection.onStdin(handler)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.onInput(input1) >> { received.countDown(); throw new RuntimeException() }
+        0 * handler._
+    }
+
+    def "notifies disconnect handler on disconnect"() {
+        Runnable handler = Mock()
+        def received = new CountDownLatch(1)
+
+        when:
+        daemonConnection.onDisconnect(handler)
+        connection.disconnect()
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.run() >> { received.countDown() }
+        0 * handler._
+    }
+
+    def "notifies disconnect handler when already disconnected"() {
+        Runnable handler = Mock()
+        def received = new CountDownLatch(1)
+
+        when:
+        connection.disconnect()
+        daemonConnection.onDisconnect(handler)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.run() >> { received.countDown() }
+        0 * handler._
+    }
+
+    def "does not notify disconnect handler once it has been removed"() {
+        Runnable handler = Mock()
+
+        when:
+        daemonConnection.onDisconnect(handler)
+        daemonConnection.onDisconnect(null)
+        connection.disconnect()
+        daemonConnection.stop()
+
+        then:
+        0 * handler._
+    }
+
+    def "does not notify disconnect handler on stop"() {
+        Runnable handler = Mock()
+
+        when:
+        daemonConnection.onDisconnect(handler)
+        daemonConnection.stop()
+
+        then:
+        0 * handler._
+    }
+
+    def "can stop after disconnect handler fails"() {
+        Runnable handler = Mock()
+        def received = new CountDownLatch(1)
+
+        when:
+        connection.disconnect()
+        daemonConnection.onDisconnect(handler)
+        received.await()
+        daemonConnection.stop()
+
+        then:
+        1 * handler.run() >> { received.countDown(); throw new RuntimeException() }
+        0 * handler._
+    }
+
+    def "receive queues incoming messages"() {
+        when:
+        connection.queueIncoming("incoming1")
+        connection.queueIncoming("incoming2")
+        connection.queueIncoming("incoming3")
+        def result = []
+        result << daemonConnection.receive(20, TimeUnit.SECONDS)
+        result << daemonConnection.receive(20, TimeUnit.SECONDS)
+        result << daemonConnection.receive(20, TimeUnit.SECONDS)
+        daemonConnection.stop()
+
+        then:
+        result == ["incoming1", "incoming2", "incoming3"]
+    }
+
+    def "receive blocks until message available"() {
+        def waiting = new CountDownLatch(1)
+        def received = new CountDownLatch(1)
+        def result = null
+
+        when:
+        start {
+            waiting.countDown()
+            result = daemonConnection.receive(20, TimeUnit.SECONDS)
+            received.countDown()
+        }
+        waiting.await()
+        Thread.sleep(500)
+        connection.queueIncoming("incoming")
+        received.await()
+
+        then:
+        result == "incoming"
+    }
+
+    def "receive blocks until connection stopped"() {
+        def waiting = new CountDownLatch(1)
+        def result = null
+
+        when:
+        start {
+            waiting.countDown()
+            result = daemonConnection.receive(20, TimeUnit.SECONDS)
+        }
+        waiting.await()
+        Thread.sleep(500)
+        daemonConnection.stop()
+        finished()
+
+        then:
+        result == null
+    }
+
+    def "receive blocks until connection disconnected"() {
+        def waiting = new CountDownLatch(1)
+        def result = null
+
+        when:
+        start {
+            waiting.countDown()
+            result = daemonConnection.receive(20, TimeUnit.SECONDS)
+        }
+        waiting.await()
+        Thread.sleep(500)
+        connection.disconnect()
+        finished()
+
+        then:
+        result == null
+    }
+
+    def "receive blocks until timeout"() {
+        when:
+        def result = daemonConnection.receive(100, TimeUnit.MILLISECONDS)
+
+        then:
+        result == null
+    }
+
+    def "receive rethrows failure to receive from connection"() {
+        def waiting = new CountDownLatch(1)
+        def failure = new RuntimeException()
+        def result = null
+
+        when:
+        start {
+            waiting.countDown()
+            try {
+                daemonConnection.receive(20, TimeUnit.SECONDS)
+            } catch (RuntimeException e) {
+                result = e
+            }
+        }
+        waiting.await()
+        Thread.sleep(500)
+        connection.queueBroken(failure)
+        finished()
+
+        then:
+        result == failure
+    }
+
+    def "receive ignores stdin messages"() {
+        when:
+        connection.queueIncoming("incoming1")
+        connection.queueIncoming(new ForwardInput(12, "yo".bytes))
+        connection.queueIncoming(new CloseInput(44))
+        connection.queueIncoming("incoming2")
+        def result = []
+        result << daemonConnection.receive(20, TimeUnit.SECONDS)
+        result << daemonConnection.receive(20, TimeUnit.SECONDS)
+        daemonConnection.stop()
+
+        then:
+        result == ["incoming1", "incoming2"]
+    }
+
+    static class TestConnection implements Connection<Object> {
+        final Object lock = new Object()
+        final Object endInput = new Object()
+        final LinkedList<Object> receiveQueue = new LinkedList<Object>()
+
+        void requestStop() {
+        }
+
+        void dispatch(Object message) {
+        }
+
+        void queueIncoming(Object message) {
+            synchronized (lock) {
+                receiveQueue << message
+                lock.notifyAll()
+            }
+        }
+
+        void queueBroken(Throwable failure = new RuntimeException()) {
+            queueIncoming(failure)
+        }
+
+        Object receive() {
+            synchronized (lock) {
+                while (receiveQueue.empty) {
+                    lock.wait()
+                }
+                def message = receiveQueue.removeFirst()
+                if (message instanceof Throwable) {
+                    message.fillInStackTrace()
+                    throw message
+                }
+                return message == endInput ? null : message
+            }
+        }
+
+        void stop() {
+            disconnect()
+        }
+
+        void disconnect() {
+            queueIncoming(endInput)
+        }
+    }
+}
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 dab3a9d..dc380e1 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
@@ -20,7 +20,7 @@ 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 org.gradle.tooling.internal.provider.connection.ProviderOperationParameters
 import spock.lang.Specification
 
 class DaemonGradleLauncherActionExecuterTest extends Specification {
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 5e16888..dd15ebb 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
@@ -20,7 +20,7 @@ import org.gradle.initialization.GradleLauncherAction
 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 org.gradle.tooling.internal.provider.connection.ProviderOperationParameters
 import spock.lang.Specification
 
 class LoggingBridgingGradleLauncherActionExecuterTest extends Specification {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/AdaptedOperationParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/AdaptedOperationParametersTest.groovy
new file mode 100644
index 0000000..7f73ec7
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/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.connection
+
+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/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
deleted file mode 100644
index 7961c90..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/input/AdaptedOperationParametersTest.groovy
+++ /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.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/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
new file mode 100644
index 0000000..74a2ddc
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+
+/**
+ * by Szczepan Faber, created at: 9/4/12
+ */
+class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule public final TestResources resources = new TestResources()
+
+    def "multiModule"() {
+        given:
+        file("build.gradle") << "apply plugin: 'maven2Gradle'"
+
+        when:
+        run 'maven2Gradle'
+
+        then:
+        file("settings.gradle").exists()
+
+        when:
+        run '-i', 'clean', 'build'
+
+        then: //smoke test the build artifacts
+        file("webinar-api/build/libs/webinar-api-1.0-SNAPSHOT.jar").exists()
+        file("webinar-impl/build/libs/webinar-impl-1.0-SNAPSHOT.jar").exists()
+        file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
+        file('webinar-impl/build/reports/tests/index.html').exists()
+
+        new JUnitTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+
+        when:
+        run 'projects'
+
+        then:
+        output.contains(toPlatformLineSeparators("""
+Root project 'webinar-parent'
++--- Project ':webinar-api' - Webinar APIs
++--- Project ':webinar-impl' - Webinar implementation
+\\--- Project ':webinar-war' - Webinar web application
+"""))
+    }
+
+    def "flatmultimodule"() {
+            given:
+            file("webinar-parent/build.gradle") << "apply plugin: 'maven2Gradle'"
+
+            when:
+            executer.inDirectory(file("webinar-parent"))
+            run 'maven2Gradle'
+
+            then:
+            file("webinar-parent/settings.gradle").exists()
+
+            when:
+            executer.inDirectory(file("webinar-parent"))
+            run '-i', 'clean', 'build'
+
+            then: //smoke test the build artifacts
+            file("webinar-api/build/libs/webinar-api-1.0-SNAPSHOT.jar").exists()
+            file("webinar-impl/build/libs/webinar-impl-1.0-SNAPSHOT.jar").exists()
+            file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
+            file('webinar-impl/build/reports/tests/index.html').exists()
+
+            new JUnitTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+
+            when:
+            executer.inDirectory(file("webinar-parent"))
+            run 'projects'
+
+            then:
+            output.contains(toPlatformLineSeparators("""
+Root project 'webinar-parent'
++--- Project ':webinar-api' - Webinar APIs
++--- Project ':webinar-impl' - Webinar implementation
+\\--- Project ':webinar-war' - Webinar web application
+"""))
+        }
+
+    def "singleModule"() {
+        given:
+        file("build.gradle") << "apply plugin: 'maven2Gradle'"
+
+        when:
+        run 'maven2Gradle'
+
+        then:
+        noExceptionThrown()
+
+        when:
+        //TODO SF this build should fail because the TestNG test is failing
+        //however the plugin does not generate testNG for single module project atm (bug)
+        //def failure = runAndFail('clean', 'build')  //assert if fails for the right reason
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/util-2.5.jar").exists()
+    }
+
+    def "testjar"() {
+        given:
+        file("build.gradle") << "apply plugin: 'maven2Gradle'"
+
+        when:
+        run 'maven2Gradle'
+
+        then:
+        noExceptionThrown()
+
+        when:
+        run 'clean', 'build'
+
+        then:
+        file("build/libs/testjar-2.5.jar").exists()
+        file("build/libs/testjar-2.5-tests.jar").exists()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml
new file mode 100644
index 0000000..d02f781
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/pom.xml
@@ -0,0 +1,20 @@
+<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>
+
+  <artifactId>webinar-api</artifactId>
+  <packaging>jar</packaging>
+  <name>Webinar APIs</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <relativePath>../webinar-parent</relativePath>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
new file mode 100644
index 0000000..a7fe90f
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-api/src/main/java/webinar/Demoable.java
@@ -0,0 +1,5 @@
+package webinar;
+
+public interface Demoable {
+  String getDescription();
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml
new file mode 100644
index 0000000..0ef5dca
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/pom.xml
@@ -0,0 +1,39 @@
+<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>
+
+  <artifactId>webinar-impl</artifactId>
+  <packaging>jar</packaging>
+  <name>Webinar implementation</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <relativePath>../webinar-parent</relativePath>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.6</version>
+    </dependency>
+  
+    <dependency>
+      <groupId>org.gradle.webinar</groupId>
+      <artifactId>webinar-api</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+  </dependencies>
+  
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
new file mode 100644
index 0000000..7678df1
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/main/java/webinar/Webinar.java
@@ -0,0 +1,20 @@
+package webinar;
+
+import org.apache.commons.lang.StringUtils;
+
+public class Webinar implements Demoable {
+  
+  private final String description;
+  
+  public Webinar() {
+    this("I'm happy today!");
+  }
+  
+  public Webinar(String description) {
+    this.description = description;
+  }
+
+  public String getDescription() {
+    return StringUtils.normalizeSpace(description);
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
new file mode 100644
index 0000000..0cbc918
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-impl/src/test/java/webinar/WebinarTest.java
@@ -0,0 +1,15 @@
+package webinar;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebinarTest {
+  
+  @Test public void normalizesDescription() {
+    //when
+    Demoable demoable = new Webinar("nice   day");
+    
+    //then
+    Assert.assertEquals("nice day", demoable.getDescription());
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml
new file mode 100644
index 0000000..529c741
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml
@@ -0,0 +1,30 @@
+<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.webinar</groupId>
+  <artifactId>webinar-parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>junit</groupId>
+				<artifactId>junit</artifactId>
+				<version>4.10</version>
+				<scope>test</scope>
+			</dependency>
+		</dependencies>
+  </dependencyManagement>
+    
+  <modules>
+    <module>../webinar-api</module>
+    <module>../webinar-impl</module>
+    <module>../webinar-war</module>    
+  </modules>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml
new file mode 100644
index 0000000..d6b8657
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/pom.xml
@@ -0,0 +1,37 @@
+<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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>webinar-war</artifactId>
+  <packaging>war</packaging>
+  <name>Webinar web application</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <relativePath>../webinar-parent</relativePath>
+  </parent>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.gradle.webinar</groupId>
+      <artifactId>webinar-impl</artifactId>
+      <version>1.0-SNAPSHOT</version>    
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+	    <plugin>
+    	    <groupId>org.mortbay.jetty</groupId>
+        	<artifactId>maven-jetty-plugin</artifactId>        
+        	<version>6.1.26</version>
+        </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c9ba3b2
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE web-app PUBLIC
+ "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd" >
+
+<web-app>
+  <display-name>Webinar war</display-name>
+</web-app>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
new file mode 100644
index 0000000..3a5211f
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/flatmultimodule/webinar-war/src/main/webapp/index.jsp
@@ -0,0 +1,6 @@
+<%@ page import="webinar.*" %>
+<html>
+<body>
+<h2>The webinar says: <%=new Webinar().getDescription()%></h2>
+</body>
+</html>
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/pom.xml
new file mode 100644
index 0000000..f122c9f
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/pom.xml
@@ -0,0 +1,30 @@
+<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.webinar</groupId>
+  <artifactId>webinar-parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>junit</groupId>
+				<artifactId>junit</artifactId>
+				<version>4.10</version>
+				<scope>test</scope>
+			</dependency>
+		</dependencies>
+  </dependencyManagement>
+    
+  <modules>
+    <module>webinar-api</module>
+    <module>webinar-impl</module>
+    <module>webinar-war</module>    
+  </modules>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
new file mode 100644
index 0000000..1bc24a0
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/pom.xml
@@ -0,0 +1,19 @@
+<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>
+
+  <artifactId>webinar-api</artifactId>
+  <packaging>jar</packaging>
+  <name>Webinar APIs</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java
new file mode 100644
index 0000000..a7fe90f
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-api/src/main/java/webinar/Demoable.java
@@ -0,0 +1,5 @@
+package webinar;
+
+public interface Demoable {
+  String getDescription();
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
new file mode 100644
index 0000000..2871516
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/pom.xml
@@ -0,0 +1,38 @@
+<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>
+
+  <artifactId>webinar-impl</artifactId>
+  <packaging>jar</packaging>
+  <name>Webinar implementation</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.6</version>
+    </dependency>
+  
+    <dependency>
+      <groupId>org.gradle.webinar</groupId>
+      <artifactId>webinar-api</artifactId>
+      <version>1.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+  </dependencies>
+  
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java
new file mode 100644
index 0000000..7678df1
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/main/java/webinar/Webinar.java
@@ -0,0 +1,20 @@
+package webinar;
+
+import org.apache.commons.lang.StringUtils;
+
+public class Webinar implements Demoable {
+  
+  private final String description;
+  
+  public Webinar() {
+    this("I'm happy today!");
+  }
+  
+  public Webinar(String description) {
+    this.description = description;
+  }
+
+  public String getDescription() {
+    return StringUtils.normalizeSpace(description);
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java
new file mode 100644
index 0000000..0cbc918
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-impl/src/test/java/webinar/WebinarTest.java
@@ -0,0 +1,15 @@
+package webinar;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class WebinarTest {
+  
+  @Test public void normalizesDescription() {
+    //when
+    Demoable demoable = new Webinar("nice   day");
+    
+    //then
+    Assert.assertEquals("nice day", demoable.getDescription());
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
new file mode 100644
index 0000000..ccf1732
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/pom.xml
@@ -0,0 +1,36 @@
+<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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>webinar-war</artifactId>
+  <packaging>war</packaging>
+  <name>Webinar web application</name>
+  
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.gradle.webinar</groupId>
+      <artifactId>webinar-impl</artifactId>
+      <version>1.0-SNAPSHOT</version>    
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+	    <plugin>
+    	    <groupId>org.mortbay.jetty</groupId>
+        	<artifactId>maven-jetty-plugin</artifactId>        
+        	<version>6.1.26</version>
+        </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c9ba3b2
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE web-app PUBLIC
+ "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd" >
+
+<web-app>
+  <display-name>Webinar war</display-name>
+</web-app>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp
new file mode 100644
index 0000000..3a5211f
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/multiModule/webinar-war/src/main/webapp/index.jsp
@@ -0,0 +1,6 @@
+<%@ page import="webinar.*" %>
+<html>
+<body>
+<h2>The webinar says: <%=new Webinar().getDescription()%></h2>
+</body>
+</html>
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/pom.xml
new file mode 100644
index 0000000..4789940
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/pom.xml
@@ -0,0 +1,23 @@
+<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>util</groupId>
+  <artifactId>util</artifactId>
+  <version>2.5</version>
+  <packaging>jar</packaging>
+
+		<dependencies>
+			<dependency>
+        <groupId>commons-lang</groupId>
+        <artifactId>commons-lang</artifactId>
+        <version>2.6</version>
+			</dependency>
+			<dependency>
+				<groupId>org.testng</groupId>
+				<artifactId>testng</artifactId>
+				<version>6.7</version>
+				<scope>test</scope>
+			</dependency>
+		</dependencies>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java
new file mode 100644
index 0000000..926c3dc
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/main/java/Foo.java
@@ -0,0 +1,7 @@
+import org.apache.commons.lang.StringUtils;
+
+public class Foo {
+  public String toString() {
+    return StringUtils.normalizeSpace("hi  there!");
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java
new file mode 100644
index 0000000..673ba0b
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/singleModule/src/test/java/FooTest.java
@@ -0,0 +1,7 @@
+import org.testng.annotations.Test;
+
+public class FooTest {
+  @Test public void test() {
+    assert false: "test failure";
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/pom.xml b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/pom.xml
new file mode 100644
index 0000000..9d06737
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/pom.xml
@@ -0,0 +1,38 @@
+<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>util</groupId>
+    <artifactId>testjar</artifactId>
+    <version>2.5</version>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java
new file mode 100644
index 0000000..926c3dc
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/main/java/Foo.java
@@ -0,0 +1,7 @@
+import org.apache.commons.lang.StringUtils;
+
+public class Foo {
+  public String toString() {
+    return StringUtils.normalizeSpace("hi  there!");
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java
new file mode 100644
index 0000000..652c6fc
--- /dev/null
+++ b/subprojects/maven/src/integTest/resources/org/gradle/api/plugins/maven/MavenConversionIntegrationTest/testjar/src/test/java/FooTest.java
@@ -0,0 +1,8 @@
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class FooTest {
+  @Test public void test() {
+    assertTrue(true);
+  }
+}
\ No newline at end of file
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 e186f11..bc42495 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
@@ -31,8 +31,11 @@ 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.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
+import javax.inject.Inject;
+
 /**
  * <p>A {@link org.gradle.api.Plugin} which allows project artifacts to be deployed to a Maven repository, or installed
  * to the local Maven cache.</p>
@@ -50,6 +53,13 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
 
     public static final String INSTALL_TASK_NAME = "install";
 
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+
+    @Inject
+    public MavenPlugin(Factory<LoggingManagerInternal> loggingManagerFactory) {
+        this.loggingManagerFactory = loggingManagerFactory;
+    }
+
     public void apply(final ProjectInternal project) {
         project.getPlugins().apply(BasePlugin.class);
 
@@ -57,7 +67,7 @@ public class MavenPlugin implements Plugin<ProjectInternal> {
         final MavenPluginConvention pluginConvention = addConventionObject(project, mavenFactory);
         final DefaultDeployerFactory deployerFactory = new DefaultDeployerFactory(
                 mavenFactory,
-                project.getServices().getFactory(LoggingManagerInternal.class),
+                loggingManagerFactory,
                 project.getFileResolver(),
                 pluginConvention,
                 project.getConfigurations(),
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
new file mode 100644
index 0000000..c99233a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.internal.artifacts.DependencyManagementServices
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.plugins.maven.internal.Maven2Gradle
+import org.gradle.api.plugins.maven.internal.MavenProjectsCreator
+import org.gradle.api.tasks.TaskAction
+
+import org.gradle.api.internal.DocumentationRegistry
+import javax.inject.Inject
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
+import org.gradle.api.Incubating
+
+/**
+ * by Szczepan Faber, created at: 8/1/12
+ */
+ at Incubating
+class ConvertMaven2Gradle extends DefaultTask {
+    private final static Logger LOG = Logging.getLogger(ConvertMaven2Gradle.class)
+
+    private final DocumentationRegistry documentationRegistry
+    private final MavenSettingsProvider settingsProvider
+
+    @Inject
+    ConvertMaven2Gradle(DocumentationRegistry documentationRegistry, DependencyManagementServices managementServices) {
+        this.documentationRegistry = documentationRegistry
+        this.settingsProvider = managementServices.get(MavenSettingsProvider)
+    }
+
+    @TaskAction
+    void convertNow() {
+        LOG.lifecycle("""
+---------------
+Maven to Gradle conversion is an "incubating" feature, which means it is still in development.
+See ${documentationRegistry.featureLifecycle} for more on "incubating" features.
+Please use it, report any problems and share your feedback with us.
+---------------
+""")
+
+
+        def settings = settingsProvider.buildSettings()
+
+        def mavenProjects = new MavenProjectsCreator().create(settings, project.file("pom.xml"))
+
+        new Maven2Gradle(mavenProjects).convert()
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/Maven2GradlePlugin.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/Maven2GradlePlugin.groovy
new file mode 100644
index 0000000..cb89b74
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/Maven2GradlePlugin.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.api.plugins.maven
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * by Szczepan Faber, created at: 8/1/12
+ */
+ at Incubating
+class Maven2GradlePlugin implements Plugin<Project>{
+    void apply(Project project) {
+        project.task("maven2Gradle", type: ConvertMaven2Gradle) {
+            group = 'Bootstrap experimental'
+            description = '[incubating] Attempts to generate gradle builds from maven project.'
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
new file mode 100644
index 0000000..7e212db
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/Maven2Gradle.groovy
@@ -0,0 +1,552 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.maven.internal
+
+import org.gradle.mvn3.org.apache.maven.project.MavenProject
+import org.gradle.util.GFileUtils
+
+/**
+ * This script obtains  the effective pom of the current project, reads its dependencies
+ * and generates build.gradle scripts. It also generates settings.gradle for multimodule builds. <br/>
+ *
+ * It currently supports both single-module and multi-module POMs, inheritance, dependency management, properties - everything.
+ *
+ * @author Antony Stubbs <antony.stubbs at gmail.com>
+ * @author Baruch Sadogursky <jbaruch at sadogursky.com>
+ * */
+class Maven2Gradle {
+
+  def dependentWars = []
+  def qualifiedNames
+  def workingDir
+  def effectivePom
+
+  private Set<MavenProject> mavenProjects
+
+  Maven2Gradle(Set<MavenProject> mavenProjects) {
+      assert !mavenProjects.empty : "No maven projects provided."
+      this.mavenProjects = mavenProjects
+  }
+
+  def convert() {
+    workingDir = new File('.').canonicalFile
+    println "Working path:" + workingDir.absolutePath + "\n"
+
+      //For now we're building the effective pom xml from the model
+      //and then we parse the xml using slurper.
+      //This way we don't have to rewrite the Maven2Gradle just yet.
+      //Maven2Gradle should be rewritten (with coverage) so that feeds of the maven object model, not xml.
+      def effectivePom = new MavenProjectXmlWriter().toXml(mavenProjects)
+      //use the Groovy XmlSlurper library to parse the text string
+      this.effectivePom = new XmlSlurper().parseText(effectivePom)
+
+    String build
+    def multimodule = this.effectivePom.name() == "projects"
+
+    if (multimodule) {
+      println "This is multi-module project.\n"
+      def allProjects = this.effectivePom.project
+      print "Generating settings.gradle... "
+      qualifiedNames = generateSettings(workingDir.getName(), allProjects[0].artifactId, allProjects);
+      println "Done."
+      print "Configuring Dependencies... "
+      def dependencies = [:];
+      allProjects.each { project ->
+        dependencies[project.artifactId.text()] = getDependencies(project, allProjects)
+      }
+      println "Done."
+
+
+      def commonDeps = dependencies.get(allProjects[0].artifactId.text())
+      build = """allprojects  {
+  apply plugin: 'maven'
+
+  ${getArtifactData(allProjects[0])}
+}
+
+subprojects {
+  apply plugin: 'java'
+  ${compilerSettings(allProjects[0], "  ")}
+  ${packageSources(allProjects[0])}
+  ${getRepositoriesForProjects(allProjects)}
+  ${globalExclusions(allProjects[0])}
+  ${commonDeps}
+  ${testNg(commonDeps)}
+}
+"""
+      modules(allProjects, false).each { module ->
+        def id = module.artifactId.text()
+        String moduleDependencies = dependencies.get(id)
+        boolean warPack = module.packaging.text().equals("war")
+        def hasDependencies = !(moduleDependencies == null || moduleDependencies.length() == 0)
+        print "Generating build.gradle for module ${id}... "
+        File submoduleBuildFile = new File(projectDir(module), 'build.gradle')
+        def group = ''
+        if (module.groupId != allProjects[0].groupId) {
+          group = "group = '${module.groupId}'"
+        }
+        String moduleBuild = """
+${group}
+description = '${module.name}'
+
+"""
+        if (warPack) {
+          moduleBuild += """apply plugin: 'war'
+
+"""
+          if (dependentWars.any {project ->
+            project.groupId.text() == module.groupId.text() &&
+                    project.artifactId.text() == id
+          }) {
+            moduleBuild += """jar.enabled = true
+"""
+          }
+        }
+        if (hasDependencies) {
+          moduleBuild += moduleDependencies
+        }
+
+        moduleBuild += testNg(moduleDependencies)
+
+        if (submoduleBuildFile.exists()) {
+          print "(backing up existing one)... "
+          submoduleBuildFile.renameTo(new File("build.gradle.bak"))
+        }
+        def packageTests = packageTests(module);
+        if (packageTests) {
+          moduleBuild += packageTests;
+        }
+        submoduleBuildFile.text = moduleBuild
+        println "Done."
+      }
+      //TODO deployment
+      def uploadArchives = {
+        """
+
+
+uploadArchives {
+  group = 'Maven'
+  description = "Does a maven deploy of archives artifacts."
+
+  repositories.mavenDeployer {
+        name = 'sshDeployer' // optional
+        repository(url: "http://repos.mycompany.com/releases") {
+            authentication(userName: "me", password: "myPassword")
+        }
+      configurePom(pom)
+    }
+}
+
+
+"""
+      }
+    } else {//simple
+      println "This is single module project."
+      build = """apply plugin: 'java'
+apply plugin: 'maven'
+
+${getArtifactData(this.effectivePom)}
+
+description = \"""${this.effectivePom.name}\"""
+
+${compilerSettings(this.effectivePom, "")}
+${globalExclusions(this.effectivePom)}
+
+"""
+
+      print "Configuring Maven repositories... "
+      Set<String> repoSet = new LinkedHashSet<String>();
+      getRepositoriesForModule(this.effectivePom, repoSet)
+      String repos = """repositories {
+        $localRepoUri
+"""
+      repoSet.each {
+        repos = "${repos} ${it}\n"
+      }
+      build += "${repos}}\n"
+      println "Done."
+      print "Configuring Dependencies... "
+      String dependencies = getDependencies(this.effectivePom, null)
+      build += dependencies
+      println "Done."
+
+      String packageTests = packageTests(this.effectivePom);
+      if (packageTests) {
+        build += '//packaging tests'
+        build += packageTests;
+      }
+      print "Generating settings.gradle if needed... "
+      generateSettings(workingDir.getName(), this.effectivePom.artifactId, null);
+      println "Done."
+
+    }
+    print "Generating main build.gradle... "
+    def buildFile = new File("build.gradle")
+    if (buildFile.exists()) {
+      print "(backing up existing one)... "
+      buildFile.renameTo(new File("build.gradle.bak"))
+    }
+    buildFile.text = build
+    println "Done."
+  }
+
+  def globalExclusions = {project ->
+    def exclusions = ''
+    def enforcerPlugin = plugin('maven-enforcer-plugin', project)
+    def enforceGoal = pluginGoal('enforce', enforcerPlugin)
+    if (enforceGoal) {
+      exclusions += 'configurations.allObjects {\n'
+      enforceGoal.configuration.rules.bannedDependencies.excludes.childNodes().each {
+        def tokens = it.text().tokenize(':')
+        exclusions += "it.exclude group: '${tokens[0]}'"
+        if (tokens.size() > 1 && tokens[1] != '*') {
+          exclusions += ", module: '${tokens[1]}'"
+        }
+        exclusions += '\n'
+      }
+    }
+    exclusions = exclusions ? exclusions += '}' : exclusions
+    exclusions
+  }
+
+  def testNg = {moduleDependencies ->
+    if (moduleDependencies.contains('testng')) {
+      """test.useTestNG()
+"""
+    } else {
+      ''
+    }
+  }
+
+  def modules = {allProjects, incReactors ->
+    return allProjects.findAll { project ->
+      project.parent.text().length() > 0 && (incReactors || project.packaging.text() != 'pom')
+    }
+  }
+
+  def fqn = {project, allProjects ->
+    def buffer = new StringBuilder()
+    generateFqn(project, allProjects, buffer)
+    return buffer.toString()
+  }
+
+  private generateFqn(def project, def allProjects, StringBuilder buffer) {
+    def artifactId = project.artifactId.text()
+    buffer.insert(0, ":${artifactId}")
+    //we don't need the top-level parent in gradle, so we stop on it
+    if (project.parent.artifactId.text() != allProjects[0].artifactId.text()) {
+      generateFqn(allProjects.find {fullProject ->
+        fullProject.artifactId.text() == project.parent.artifactId.text()
+      }, allProjects, buffer)
+    }
+  }
+
+
+  def localRepoUri = {
+    """mavenLocal()
+    """
+  }
+
+  private String getArtifactData(project) {
+    return """group = '$project.groupId'
+  version = '$project.version'
+  """;
+  }
+
+  private String getRepositoriesForProjects(projects) {
+    print 'Configuring Repositories... '
+    String repos = """repositories {
+    ${localRepoUri()}
+"""
+    def repoSet = new LinkedHashSet<String>();
+    projects.each {
+      getRepositoriesForModule(it, repoSet)
+    }
+    repoSet.each {
+      repos = "${repos}${it}\n"
+    }
+    repos = "${repos}  }\n"
+    println "Done."
+    return repos
+  }
+
+  private void getRepositoriesForModule(module, repoSet) {
+    module.repositories.repository.each {
+      repoSet.add("    mavenRepo url: \"${it.url}\"")
+    }
+    //No need to include plugin repos - who cares about maven plugins?
+  }
+
+  private String getDependencies(project, allProjects) {
+// use GPath to navigate the object hierarchy and retrieve the collection of dependency nodes.
+    def dependencies = project.dependencies.dependency
+    def war = project.packaging == "war"
+
+    def compileTimeScope = []
+    def runTimeScope = []
+    def testScope = []
+    def providedScope = []
+    def systemScope = []
+
+    //cleanup duplicates from parent
+
+// using Groovy Looping and mapping a Groovy Closure to each element, we collect together all
+// the dependency nodes into corresponding collections depending on their scope value.
+    dependencies.each() {
+      if (!duplicateDependency(it, project, allProjects)) {
+        def scope = (elementHasText(it.scope)) ? it.scope : "compile"
+        switch (scope) {
+          case "compile":
+            compileTimeScope.add(it)
+            break
+          case "test":
+            testScope.add(it)
+            break
+          case "provided":
+            providedScope.add(it)
+            break
+          case "runtime":
+            runTimeScope.add(it)
+            break
+          case "system":
+            systemScope.add(it)
+            break
+        }
+      }
+    }
+
+    /**
+     * print function then checks the exclusions node to see if it exists, if
+     * so it branches off, otherwise we call our simple print function
+     */
+    def createGradleDep = {String scope, StringBuilder sb, mavenDependency ->
+      def projectDep = allProjects.find { prj ->
+        return prj.artifactId.text() == mavenDependency.artifactId.text() && prj.groupId.text() == mavenDependency.groupId.text()
+      }
+      if (projectDep) {
+        createProjectDependency(projectDep, sb, scope, allProjects)
+      } else {
+        def dependencyProperties = null;
+        if (!war && scope == 'providedCompile') {
+          scope = 'compile'
+          dependencyProperties = [provided: true]
+        }
+        def exclusions = mavenDependency.exclusions.exclusion
+        if (exclusions.size() > 0 || dependencyProperties) {
+          createComplexDependency(mavenDependency, sb, scope, dependencyProperties)
+        } else {
+          createBasicDependency(mavenDependency, sb, scope)
+        }
+      }
+    }
+
+
+    StringBuilder build = new StringBuilder()
+    if (!compileTimeScope.isEmpty() || !runTimeScope.isEmpty() || !testScope.isEmpty() || !providedScope.isEmpty() || !systemScope.isEmpty()) {
+      build.append("dependencies {").append("\n")
+// for each collection, one at a time, we take each element and call our print function
+      if (!compileTimeScope.isEmpty()) { compileTimeScope.each() { createGradleDep("compile", build, it) } }
+      if (!runTimeScope.isEmpty()) { runTimeScope.each() { createGradleDep("runtime", build, it) } }
+      if (!testScope.isEmpty()) { testScope.each() { createGradleDep("testCompile", build, it) } }
+      if (!providedScope.isEmpty()) { providedScope.each() { createGradleDep("providedCompile", build, it) } }
+      if (!systemScope.isEmpty()) { systemScope.each() { createGradleDep("system", build, it) } }
+      build.append("  }\n")
+    }
+    return build.toString();
+  }
+
+  def compilerSettings = {project, indent ->
+    def configuration = plugin('maven-compiler-plugin', project).configuration
+    return "sourceCompatibility = ${configuration.source.text() ?: '1.5'}\n${indent}targetCompatibility = ${configuration.target.text() ?: '1.5'}\n"
+  }
+
+  def plugin = {artifactId, project ->
+    project.build.plugins.plugin.find {pluginTag ->
+      pluginTag.artifactId.text() == artifactId
+    }
+  }
+
+  def pluginGoal = { goalName, plugin ->
+    plugin.executions.execution.find { exec ->
+      exec.goals.goal.find {gl ->
+        gl.text().startsWith(goalName)
+      }
+    }
+  }
+
+  def packSources = {sourceSets ->
+    def sourceSetStr = ''
+    if (!sourceSets.empty) {
+      sourceSetStr = """task packageSources(type: Jar) {
+classifier = 'sources'
+"""
+      sourceSets.each { sourceSet ->
+        sourceSetStr += """from sourceSets.${sourceSet}.allSource
+"""
+      }
+      sourceSetStr += """
+}
+artifacts.archives packageSources"""
+    }
+    println 'Done.'
+    sourceSetStr
+  }
+
+
+  def packageTests = {project ->
+    print 'Adding tests packaging...'
+    def jarPlugin = plugin('maven-jar-plugin', project)
+    pluginGoal('test-jar', jarPlugin) ? """
+task packageTests(type: Jar) {
+  from sourceSets.test.output
+  classifier = 'tests'
+}
+artifacts.archives packageTests
+""" : ''
+  }
+
+  def packageSources = { project ->
+    def sourcePlugin = plugin('maven-source-plugin', project)
+    def sourceSets = []
+    if (sourcePlugin) {
+      println 'Adding sources packaging...'
+      if (pluginGoal('jar', sourcePlugin)) {
+        sourceSets += 'main'
+      } else if (pluginGoal('test-jar', sourcePlugin)) {
+        sourceSets += 'test'
+      }
+    }
+    packSources(sourceSets)
+  }
+
+  private boolean duplicateDependency(dependency, project, allProjects) {
+    def parentTag = project.parent
+    if (allProjects == null || parentTag.isEmpty()) {//simple project or no parent
+      return false;
+    } else {
+      def parent = allProjects.find {
+        it.groupId.equals(parentTag.groupId) && it.artifactId.equals(parentTag.artifactId)
+      }
+      def duplicate = parent.dependencies.dependency.any {
+        it.groupId.equals(dependency.groupId) && it.artifactId.equals(dependency.artifactId)
+      }
+      if (duplicate) {
+        return true;
+      } else {
+        duplicateDependency(dependency, parent, allProjects)
+      }
+    }
+  }
+
+  def artifactId = {File dir ->
+    return new XmlSlurper().parse(new File(dir, 'pom.xml')).artifactId.text()
+  }
+
+  def projectDir = {project ->
+    return new File(project.build.directory.text()).parentFile
+  }
+
+  private def generateSettings(def dirName, def mvnProjectName, def projects) {
+    def qualifiedNames = [:]
+    def projectName = "";
+    if (dirName != mvnProjectName) {
+      projectName = """rootProject.name = '${mvnProjectName}'
+"""
+    }
+    def modulePoms = modules(projects, true)
+
+    def modules = new StringBuilder();
+    def artifactIdToDir = [:]
+    if (projects) {
+      modulePoms.each { project ->
+        def fqn = fqn(project, projects)
+        artifactIdToDir[fqn] = GFileUtils.relativePath(workingDir, projectDir(project))
+        modules.append("'${fqn}', ")
+      }
+      def strLength = modules.length()
+      if (strLength > 2) {
+        modules.delete(strLength - 2, strLength)
+      }
+    }
+    File settingsFile = new File("settings.gradle")
+    if (settingsFile.exists()) {
+      print "(backing up existing one)... "
+      settingsFile.renameTo(new File("settings.gradle.bak"))
+    }
+    def settingsText = "${projectName}${modules.length() > 0 ? "include ${modules.toString()}" : ''}\n"
+    artifactIdToDir.each {entry ->
+      settingsText += """
+project('$entry.key').projectDir = """ + '"$rootDir/' + "${entry.value}" + '" as File'
+    }
+    settingsFile.text = settingsText
+    return qualifiedNames
+  }
+
+/**
+ * complex print statement does one extra task which is
+ * iterate over each <exclusion> node and print out the artifact id.
+ * It also tackles the properties attached to dependencies
+ */
+  private def createComplexDependency(it, build, scope, Map dependencyProperties) {
+    build.append("    ${scope}(${contructSignature(it)}) {\n")
+    it.exclusions.exclusion.each() {
+      build.append("exclude(module: '${it.artifactId}')\n")
+    }
+    if (dependencyProperties) {
+      dependencyProperties.keySet().each { key ->
+        build.append("$key : ${dependencyProperties.get(key)}\n")
+      }
+    }
+    build.append("}\n")
+  }
+
+/**
+ * Print out the basic form og gradle dependency
+ */
+  private def createBasicDependency(mavenDependency, build, String scope) {
+    def classifier = contructSignature(mavenDependency)
+    build.append("    ${scope} ${classifier}\n")
+  }
+/**
+ * Print out the basic form of gradle dependency
+ */
+  private def createProjectDependency(projectDep, build, String scope, allProjects) {
+    if (projectDep.packaging.text() == 'war') {
+      dependentWars += projectDep
+    }
+    build.append("  ${scope} project('${fqn(projectDep, allProjects)}')\n")
+  }
+
+/**
+ * Construct and return the signature of a dependency, including it's version and
+ * classifier if it exists
+ */
+  private def contructSignature(mavenDependency) {
+    def gradelDep = "group: '${mavenDependency.groupId.text()}', name: '${mavenDependency.artifactId.text()}', version:'${mavenDependency?.version?.text()}'"
+    def classifier = elementHasText(mavenDependency.classifier) ? gradelDep + ", classifier:'" + mavenDependency.classifier.text().trim() + "'": gradelDep
+    return classifier
+  }
+
+/**
+ * Check to see if the selected node has content
+ */
+  private boolean elementHasText(it) {
+    return it.text().length() != 0
+  }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java
new file mode 100644
index 0000000..8789c1e
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.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.plugins.maven.internal;
+
+import org.gradle.mvn3.org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.gradle.mvn3.org.apache.maven.project.MavenProject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 9/14/12
+ */
+public class MavenProjectXmlWriter {
+
+    //TODO SF this class attempts to mimic the behavior of the output of mvn help:effective-pom
+    //we can remove it when the conversion feature no longer depends on the effective xml
+    //if we want to keep this class, we need to add more tests.
+
+    String toXml(Set<MavenProject> projects) {
+        assert !projects.isEmpty() : "Cannot prepare the maven projects effective xml because provided projects set is empty.";
+
+        if (projects.size() == 1) {
+            return toXml(projects.iterator().next());
+        }
+
+        StringBuilder out = new StringBuilder("<projects>");
+        for (MavenProject project : projects) {
+            out.append(toXml(project));
+        }
+        return out.append("</projects>").toString();
+    }
+
+    private String toXml(MavenProject project) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            new MavenXpp3Writer().write(out, project.getModel());
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to serialize maven model to xml. Maven project: " + project, e);
+        }
+        return out.toString().replace("<?xml version=\"1.0\"?>", "");
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreator.java
new file mode 100644
index 0000000..214efa0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreator.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.plugins.maven.internal;
+
+import com.google.common.collect.ImmutableList;
+import org.gradle.mvn3.org.apache.maven.execution.*;
+import org.gradle.mvn3.org.apache.maven.model.building.ModelBuildingRequest;
+import org.gradle.mvn3.org.apache.maven.project.*;
+import org.gradle.mvn3.org.apache.maven.settings.Settings;
+import org.gradle.mvn3.org.codehaus.plexus.ContainerConfiguration;
+import org.gradle.mvn3.org.codehaus.plexus.DefaultContainerConfiguration;
+import org.gradle.mvn3.org.codehaus.plexus.DefaultPlexusContainer;
+import org.gradle.mvn3.org.codehaus.plexus.PlexusContainerException;
+import org.gradle.mvn3.org.codehaus.plexus.classworlds.ClassWorld;
+import org.gradle.mvn3.org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.gradle.mvn3.org.codehaus.plexus.configuration.PlexusConfigurationException;
+import org.gradle.mvn3.org.sonatype.aether.RepositorySystemSession;
+import org.gradle.mvn3.org.sonatype.aether.util.DefaultRepositorySystemSession;
+import org.gradle.api.GradleException;
+import org.gradle.api.Transformer;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 9/11/12
+ */
+public class MavenProjectsCreator {
+
+    public Set<MavenProject> create(Settings mavenSettings, File pomFile) {
+        if (!pomFile.exists()) {
+            throw new GradleException("Unable to create maven project model. The input pom file does not exist: " + pomFile);
+        }
+        try {
+            return createNow(mavenSettings, pomFile);
+        } catch (Exception e) {
+            throw new GradleException("Unable to create maven project model using pom file: " + pomFile.getAbsolutePath(), e);
+        }
+    }
+
+    private Set<MavenProject> createNow(Settings settings, File pomFile) throws PlexusContainerException, PlexusConfigurationException, ComponentLookupException, MavenExecutionRequestPopulationException, ProjectBuildingException {
+        //using jarjar for maven3 classes affects the contents of the effective pom
+        //references to certain maven standard plugins contain jarjar in the fqn
+        //not sure if this is a problem.
+        ContainerConfiguration containerConfiguration = new DefaultContainerConfiguration()
+                .setClassWorld(new ClassWorld("plexus.core", ClassWorld.class.getClassLoader()))
+                .setName("mavenCore");
+
+        DefaultPlexusContainer container = new DefaultPlexusContainer(containerConfiguration);
+        ProjectBuilder builder = container.lookup(ProjectBuilder.class);
+        MavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest();
+        MavenExecutionRequestPopulator populator = container.lookup(MavenExecutionRequestPopulator.class);
+        populator.populateFromSettings(executionRequest, settings);
+        populator.populateDefaults(executionRequest);
+        ProjectBuildingRequest buildingRequest = executionRequest.getProjectBuildingRequest();
+        buildingRequest.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
+        MavenProject mavenProject = builder.build(pomFile, buildingRequest).getProject();
+
+        Set<MavenProject> reactorProjects = new LinkedHashSet<MavenProject>();
+
+        //TODO adding the parent project first because the converter needs it this way ATM. This is oversimplified.
+        //the converter should not depend on the order of reactor projects.
+        //we should add coverage for nested multi-project builds with multiple parents.
+        reactorProjects.add(mavenProject);
+
+        List<ProjectBuildingResult> allProjects = builder.build(ImmutableList.of(pomFile), true, buildingRequest);
+        CollectionUtils.collect(allProjects, reactorProjects, new Transformer<MavenProject, ProjectBuildingResult>() {
+            public MavenProject transform(ProjectBuildingResult original) {
+                return original.getProject();
+            }
+        });
+
+        MavenExecutionResult result = new DefaultMavenExecutionResult();
+        result.setProject(mavenProject);
+        RepositorySystemSession repoSession = new DefaultRepositorySystemSession();
+        MavenSession session = new MavenSession(container, repoSession, executionRequest, result);
+        session.setCurrentProject(mavenProject);
+
+        return reactorProjects;
+    }
+}
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
deleted file mode 100644
index 73774c1..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/InstallPublications.groovy
+++ /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.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
deleted file mode 100644
index f0b82e0..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublicationPlugin.groovy
+++ /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.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
deleted file mode 100644
index f0087a5..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/Publications.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.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
deleted file mode 100644
index 04f7ba1..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublishPublications.groovy
+++ /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.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
deleted file mode 100644
index 3290109..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenArtifact.groovy
+++ /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.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
deleted file mode 100644
index 56ee2ff..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenDependency.groovy
+++ /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.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
deleted file mode 100644
index 4255b05..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPomCustomizer.groovy
+++ /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.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
deleted file mode 100644
index 5529998..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublication.groovy
+++ /dev/null
@@ -1,32 +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.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
deleted file mode 100644
index 970c2ce..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublisher.groovy
+++ /dev/null
@@ -1,23 +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.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
deleted file mode 100644
index 5cb9ac5..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenScope.groovy
+++ /dev/null
@@ -1,24 +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.publication.maven
-
-enum MavenScope {
-    COMPILE,
-    RUNTIME,
-    TEST,
-    PROVIDED,
-    SYSTEM
-}
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
index 55f529d..e202392 100644
--- 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
@@ -22,16 +22,19 @@ 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.ErroringAction;
+import org.gradle.api.internal.IoActions;
 import org.gradle.api.internal.XmlTransformer;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.listener.ActionBroadcast;
 
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
 import java.util.Collections;
 import java.util.List;
 
@@ -68,7 +71,7 @@ public class DefaultMavenPom implements MavenPom {
         this.configurations = configurations;
         return this;
     }
-    
+
     public DefaultMavenPom setGroupId(String groupId) {
         getModel().setGroupId(groupId);
         return this;
@@ -188,47 +191,26 @@ public class DefaultMavenPom implements MavenPom {
     }
 
     public DefaultMavenPom writeTo(Object path) {
-        OutputStream stream = null;
-
-        try {
-            File file = fileResolver.resolve(path);
-            if (file.getParentFile() != null) {
-                file.getParentFile().mkdirs();
+        IoActions.writeFile(fileResolver.resolve(path), new Action<BufferedWriter>() {
+            public void execute(BufferedWriter writer) {
+                writeTo(writer);
             }
-            stream = new FileOutputStream(file);
-            getEffectivePom().writeNonEffectivePom(stream);
-            return this;
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            IOUtils.closeQuietly(stream);
-        }
+        });
+        return this;
     }
 
     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);
+            withXmlActions.transform(pomWriter, "UTF-8", new ErroringAction<Writer>() {
+                protected void doExecute(Writer writer) throws IOException{
+                    mavenProject.writeModel(writer);
+                }
+            });
         } 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;
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
deleted file mode 100644
index dc32108..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPublicationPomGenerator.groovy
+++ /dev/null
@@ -1,58 +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.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/ant/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
index eaf5822..43de9da 100644
--- 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
@@ -42,10 +42,11 @@ 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.AbstractArtifactRepository;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
+import org.gradle.api.publication.maven.internal.PomFilter;
 import org.gradle.listener.ActionBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.AntUtil;
@@ -59,9 +60,7 @@ import java.util.Set;
 /**
  * @author Hans Dockter
  */
-public abstract class AbstractMavenResolver implements MavenResolver, DependencyResolver, ArtifactRepositoryInternal {
-
-    private String name;
+public abstract class AbstractMavenResolver extends AbstractArtifactRepository implements MavenResolver, DependencyResolver, ArtifactRepositoryInternal {
     
     private ArtifactPomContainer artifactPomContainer;
 
@@ -83,14 +82,6 @@ public abstract class AbstractMavenResolver implements MavenResolver, Dependency
 
     protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
 
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
     public DependencyResolver createResolver() {
         return this;
     }
@@ -297,4 +288,5 @@ public abstract class AbstractMavenResolver implements MavenResolver, Dependency
     public void beforeDeployment(Closure action) {
         beforeDeploymentActions.add(action);
     }
+
 }
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
deleted file mode 100644
index 5aa1d6f..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisher.groovy
+++ /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.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/model/DefaultMavenArtifact.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenArtifact.groovy
deleted file mode 100644
index 2328a86..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenArtifact.groovy
+++ /dev/null
@@ -1,24 +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.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
deleted file mode 100644
index 29d19be..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenDependency.groovy
+++ /dev/null
@@ -1,52 +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.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
deleted file mode 100644
index 28ad7a2..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenPublication.groovy
+++ /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.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
deleted file mode 100644
index df9c874..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/DependenciesConverter.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.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
deleted file mode 100644
index 41e8639..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publication.maven.internal.modelbuilder
-
-import org.gradle.api.Project
-import org.gradle.internal.reflect.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
deleted file mode 100644
index e5fe98c..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/CustomModelBuilder.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.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
deleted file mode 100644
index f1935f8..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/ModelFactory.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.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
deleted file mode 100644
index a0543fe..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/PlexusLoggerAdapter.java
+++ /dev/null
@@ -1,105 +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.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/main/resources/META-INF/gradle-plugins/maven2Gradle.properties b/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven2Gradle.properties
new file mode 100644
index 0000000..a4ac47a
--- /dev/null
+++ b/subprojects/maven/src/main/resources/META-INF/gradle-plugins/maven2Gradle.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.maven.Maven2GradlePlugin
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 fe8cc04..967ae16 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
@@ -39,18 +39,17 @@ import static org.junit.Assert.assertThat;
  */
 public class MavenPluginTest {
     private final DefaultProject project = HelperUtil.createRootProject();
-    private final MavenPlugin mavenPlugin = new MavenPlugin();
 
     @org.junit.Test
     public void addsConventionToProject() {
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
 
         assertThat(project.getConvention().getPlugin(MavenPluginConvention.class), Matchers.<MavenPluginConvention>notNullValue());
     }
     
     @org.junit.Test
     public void defaultConventionValues() {
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
 
         MavenPluginConvention convention = project.getConvention().getPlugin(MavenPluginConvention.class);
         assertThat(convention.getMavenPomDir(), equalTo(new File(project.getBuildDir(), "poms")));
@@ -60,7 +59,8 @@ public class MavenPluginTest {
     @org.junit.Test
     public void applyWithWarPlugin() {
         project.getPlugins().apply(WarPlugin.class);
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
+
         assertHasConfigurationAndMapping(project, WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.PROVIDED,
                 MavenPlugin.PROVIDED_COMPILE_PRIORITY);
         assertHasConfigurationAndMapping(project, WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME, Conf2ScopeMappingContainer.PROVIDED,
@@ -82,7 +82,8 @@ public class MavenPluginTest {
     @org.junit.Test
     public void applyWithJavaPlugin() {
         project.getPlugins().apply(JavaPlugin.class);
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
+
         assertHasConfigurationAndMapping(project, JavaPlugin.COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.COMPILE,
                 MavenPlugin.COMPILE_PRIORITY);
         assertHasConfigurationAndMapping(project, JavaPlugin.RUNTIME_CONFIGURATION_NAME, Conf2ScopeMappingContainer.RUNTIME,
@@ -100,7 +101,7 @@ public class MavenPluginTest {
     @org.junit.Test
     public void addsAndConfiguresAnInstallTask() {
         project.getPlugins().apply(JavaPlugin.class);
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
 
         Upload task = project.getTasks().withType(Upload.class).getByName(MavenPlugin.INSTALL_TASK_NAME);
         assertThat(task.getRepositories().get(0), instanceOf(MavenResolver.class));
@@ -109,7 +110,7 @@ public class MavenPluginTest {
     @org.junit.Test
     public void addsConventionMappingToTheRepositoryContainerOfEachUploadTask() {
         project.getPlugins().apply(JavaPlugin.class);
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
 
         Upload task = project.getTasks().withType(Upload.class).getByName(MavenPlugin.INSTALL_TASK_NAME);
         MavenRepositoryHandlerConvention convention = new DslObject(task.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
@@ -122,14 +123,16 @@ public class MavenPluginTest {
 
     @org.junit.Test
     public void applyWithoutWarPlugin() {
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
+
         assertThat(project.getConfigurations().findByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
                 nullValue());
     }
 
     @org.junit.Test
     public void applyWithoutJavaPlugin() {
-        mavenPlugin.apply(project);
+        project.getPlugins().apply(MavenPlugin.class);
+
         assertThat(project.getConfigurations().findByName(JavaPlugin.COMPILE_CONFIGURATION_NAME),
                 nullValue());
     }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/Maven2GradlePluginSpec.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/Maven2GradlePluginSpec.groovy
new file mode 100644
index 0000000..b94e43a
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/Maven2GradlePluginSpec.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.api.plugins.maven
+
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 8/1/12
+ */
+class Maven2GradlePluginSpec extends Specification {
+
+    def project = new ProjectBuilder().build()
+
+    def "applies plugin"() {
+        when:
+        project.plugins.apply Maven2GradlePlugin
+
+        then:
+        project.tasks.maven2Gradle instanceof ConvertMaven2Gradle
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
new file mode 100644
index 0000000..8553f43
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectsCreatorSpec.groovy
@@ -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.plugins.maven.internal
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider
+import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations
+import org.gradle.api.GradleException
+
+/**
+ * by Szczepan Faber, created at: 10/15/12
+ */
+class MavenProjectsCreatorSpec extends Specification {
+
+    @Rule TemporaryFolder temp
+    private settings = new DefaultMavenSettingsProvider({} as MavenFileLocations)
+    private creator = new MavenProjectsCreator()
+
+    def "creates single module project"() {
+        given:
+        def pom = temp.file("pom.xml")
+        pom.text = """<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>util</groupId>
+  <artifactId>util</artifactId>
+  <version>2.5</version>
+  <packaging>jar</packaging>
+</project>"""
+
+        when:
+        def mavenProjects = creator.create(settings.buildSettings(), pom) as List
+
+        then:
+        mavenProjects.size() == 1
+        mavenProjects[0].name == 'util'
+    }
+
+    def "creates multi module project"() {
+        given:
+        def parentPom = temp.file("pom.xml")
+        parentPom.text = """<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.gradle.webinar</groupId>
+  <artifactId>webinar-parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <modules>
+    <module>webinar-api</module>
+  </modules>
+</project>
+"""
+
+        temp.createDir("webinar-api")
+        temp.file("webinar-api/pom.xml").text = """<project>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>webinar-api</artifactId>
+  <packaging>jar</packaging>
+
+  <parent>
+    <groupId>org.gradle.webinar</groupId>
+    <artifactId>webinar-parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+</project>
+"""
+
+        when:
+        def mavenProjects = creator.create(settings.buildSettings(), parentPom) as List
+
+        then:
+        mavenProjects.size() == 2
+        mavenProjects[0].name == 'webinar-parent'
+        mavenProjects[1].name == 'webinar-api'
+    }
+
+    def "fails with decent exception if pom is incorrect"() {
+        given:
+        def pom = temp.file("pom.xml")
+        pom.text = """<project>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>util</artifactId>
+  <version>2.5</version>
+  <packaging>jar</packaging>
+</project>"""
+
+        when:
+        creator.create(settings.buildSettings(), pom) as List
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains(pom.getAbsolutePath())
+    }
+
+    def "fails with decent exception if pom does not exist"() {
+        when:
+        creator.create(settings.buildSettings(), temp.file("pom.xml")) as List
+
+        then:
+        thrown(GradleException)
+    }
+}
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
deleted file mode 100644
index d10c967..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
+++ /dev/null
@@ -1,150 +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.publication.maven.internal.ant
-
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository
-import org.gradle.api.internal.file.DefaultTemporaryFileProvider
-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 org.gradle.internal.Factory))
-    
-    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/modelbuilder/MavenPublicationBuilderTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilderTest.groovy
deleted file mode 100644
index 4fdc7dc..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilderTest.groovy
+++ /dev/null
@@ -1,209 +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.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/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java
index e3bfd83..ac2cbb3 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java
@@ -48,7 +48,7 @@ public class DefaultActorFactory implements ActorFactory, Stoppable {
     public void stop() {
         synchronized (lock) {
             try {
-                new CompositeStoppable().add(nonBlockingActors.values()).add(blockingActors.values()).stop();
+                CompositeStoppable.stoppable(nonBlockingActors.values()).add(blockingActors.values()).stop();
             } finally {
                 nonBlockingActors.clear();
             }
@@ -148,7 +148,7 @@ public class DefaultActorFactory implements ActorFactory, Stoppable {
 
         public void stop() {
             try {
-                new CompositeStoppable(dispatch, executor, failureHandler).stop();
+                CompositeStoppable.stoppable(dispatch, executor, failureHandler).stop();
             } finally {
                 stopped(this);
             }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
index 93aeb41..9ae3551 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
@@ -63,7 +63,7 @@ public class AsyncConnectionAdapter<T> implements AsyncConnection<T>, Stoppable
     }
 
     public void stop() {
-        new CompositeStoppable(stack, outgoing, connection, incoming).add(executors).stop();
+        CompositeStoppable.stoppable(stack, outgoing, connection, incoming).add(executors).stop();
     }
 
     private class ConnectionReceive<T> implements Receive<T> {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
index 1d0ead8..2a95365 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
@@ -78,7 +78,7 @@ public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
     }
 
     public void stop() {
-        new CompositeStoppable().add(protocolStack, hub, executor).stop();
+        CompositeStoppable.stoppable(protocolStack, hub, executor).stop();
     }
 
     private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
index b4cfa7a..7f77b07 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
@@ -44,6 +44,6 @@ public class DefaultMessagingClient implements MessagingClient, Stoppable {
     }
 
     public void stop() {
-        new CompositeStoppable(connections).stop();
+        CompositeStoppable.stoppable(connections).stop();
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
index d45a989..5a3e18c 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
@@ -66,7 +66,7 @@ public class DefaultMessagingServer implements MessagingServer, Stoppable {
             connection.requestStop();
         }
         try {
-            new CompositeStoppable(connections).stop();
+            CompositeStoppable.stoppable(connections).stop();
         } finally {
             connections.clear();
         }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
index e0dc24a..2719bd2 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
@@ -80,10 +80,10 @@ public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
     }
 
     public void stop() {
-        CompositeStoppable stoppable = new CompositeStoppable();
+        CompositeStoppable stoppable;
         lock.lock();
         try {
-            stoppable.add(hub, discoveryBroadcast, executor);
+            stoppable = CompositeStoppable.stoppable(hub, discoveryBroadcast, executor);
         } finally {
             connections.clear();
             lock.unlock();
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
deleted file mode 100644
index 860c3e7..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnection.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.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/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
deleted file mode 100644
index 8cfde2a..0000000
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
+++ /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.messaging.remote.internal;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.internal.concurrent.StoppableExecutor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-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 = LoggerFactory.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/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
index 9534a74..0bcc410 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
@@ -57,11 +57,7 @@ public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
     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;
@@ -76,26 +72,10 @@ public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
         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);
     }
@@ -113,10 +93,7 @@ public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
             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) {
@@ -138,7 +115,7 @@ public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
             }
         };
 
-        this.asyncReceive = new AsyncReceive(executor, dispatch, new Runnable() {
+        this.asyncReceive = new AsyncReceive<T>(executor, dispatch, new Runnable() {
             public void run() {
                 lock.lock();
                 try {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java
index 23b59d2..d467be6 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java
@@ -33,8 +33,8 @@ 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 CompositeStoppable executors = CompositeStoppable.stoppable();
+    private final CompositeStoppable connections = CompositeStoppable.stoppable();
     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>>();
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java
index 521e29e..9d20acd 100644
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java
@@ -110,7 +110,7 @@ public class ProtocolStack<T> implements AsyncStoppable {
             throw UncheckedException.throwAsUncheckedException(e);
         }
         callbackQueue.clear();
-        new CompositeStoppable(callbackQueue, receiver, workQueue, incomingQueue, outgoingQueue).stop();
+        CompositeStoppable.stoppable(callbackQueue, receiver, workQueue, incomingQueue, outgoingQueue).stop();
     }
 
     private class ExecuteRunnable implements Dispatch<Runnable> {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
index 4690853..c5ee93f 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
@@ -110,11 +110,11 @@ public class SocketConnection<T> implements Connection<T> {
     }
 
     public void requestStop() {
-        new CompositeStoppable(instr).stop();
+        CompositeStoppable.stoppable(instr).stop();
     }
 
     public void stop() {
-        new CompositeStoppable(instr, outstr, socket).stop();
+        CompositeStoppable.stoppable(instr, outstr, socket).stop();
     }
 
     private static class SocketInputStream extends InputStream {
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
index 3cd9ebd..b195e78 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
@@ -76,7 +76,7 @@ public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStopp
     }
 
     public void requestStop() {
-        new CompositeStoppable().add(serverSockets).stop();
+        CompositeStoppable.stoppable(serverSockets).stop();
     }
 
     public void stop() {
@@ -121,7 +121,7 @@ public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStopp
                     LOGGER.error("Could not accept remote connection.", e);
                 }
             } finally {
-                new CompositeStoppable(serverSocket).stop();
+                CompositeStoppable.stoppable(serverSocket).stop();
                 serverSockets.remove(serverSocket);
             }
         }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
index 3d0d1f8..e06c30a 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
@@ -39,6 +39,15 @@ public class MethodMetaInfo extends Message {
         }
     }
 
+    @Override
+    public String toString() {
+        return "MethodMetaInfo{"
+                + "type=" + type
+                + ", methodName='" + methodName + '\''
+                + ", paramTypes=" + (paramTypes == null ? null : Arrays.asList(paramTypes))
+                + '}';
+    }
+
     public Object getKey() {
         return key;
     }
@@ -111,5 +120,12 @@ public class MethodMetaInfo extends Message {
         public int hashCode() {
             return typeName.hashCode();
         }
+
+        @Override
+        public String toString() {
+            return "Type{"
+                    + "typeName='" + typeName + '\''
+                    + '}';
+        }
     }
 }
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
index 71ea1cc..9fa7d4c 100755
--- a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.messaging.remote.internal.protocol;
 
+import com.google.common.base.Objects;
 import org.gradle.messaging.remote.internal.Message;
 
 import java.util.Arrays;
@@ -53,4 +54,12 @@ public class RemoteMethodInvocation extends Message {
     public int hashCode() {
         return key.hashCode();
     }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .add("key", key)
+                .add("arguments", Arrays.toString(arguments))
+                .toString();
+    }
 }
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
deleted file mode 100644
index 5dc8d70..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
+++ /dev/null
@@ -1,168 +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.messaging.remote.internal
-
-import org.gradle.internal.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/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
index a2c289a..2157d73 100644
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
@@ -16,10 +16,14 @@
 package org.gradle.messaging.remote.internal.inet
 
 import org.gradle.api.Action
+import org.gradle.internal.id.UUIDGenerator
+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.DefaultMessageSerializer
 import org.gradle.util.ConcurrentSpecification
-import org.gradle.internal.id.UUIDGenerator
+
+import java.util.concurrent.CountDownLatch
 
 class TcpConnectorTest extends ConcurrentSpecification {
     final def serializer = new DefaultMessageSerializer<String>(getClass().classLoader)
@@ -83,4 +87,36 @@ class TcpConnectorTest extends ConcurrentSpecification {
         ConnectException e = thrown()
         e.message.startsWith "Could not connect to server ${address}."
     }
+
+    def "can receive message from peer after peer has closed connection"() {
+        // This is a test to simulate the messaging that the daemon does on build completion, in order to validate some assumptions
+
+        def closed = new CountDownLatch(1)
+
+        when:
+        def address = incomingConnector.accept({ ConnectEvent<Connection<Object>> event ->
+            def connection = event.connection
+            println "[server] connected"
+            connection.dispatch("bye")
+            connection.stop()
+            closed.countDown()
+            println "[server] disconnected"
+        } as Action, false)
+
+        def connection = outgoingConnector.connect(address)
+        println "[client] connected"
+        closed.await()
+        println "[client] receiving"
+        assert connection.receive() == "bye"
+        assert connection.receive() == null
+        connection.stop()
+        println "[client] disconnected"
+        incomingConnector.requestStop()
+
+        then:
+        finished()
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
 }
diff --git a/subprojects/migration/migration.gradle b/subprojects/migration/migration.gradle
deleted file mode 100644
index eab7c09..0000000
--- a/subprojects/migration/migration.gradle
+++ /dev/null
@@ -1,26 +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.
- */
-
-dependencies {
-    groovy libraries.groovy
-
-    compile project(":core")
-    compile project(":toolingApi")
-    compile libraries.guava
-    compile libraries.slf4j_api
-}
-
-//useTestFixtures()
diff --git a/subprojects/native/native.gradle b/subprojects/native/native.gradle
index e409e5c..04f1ac8 100755
--- a/subprojects/native/native.gradle
+++ b/subprojects/native/native.gradle
@@ -1,8 +1,6 @@
 /*
     This project contains various native operating system integration utilities.
 */
-apply from: "$rootDir/gradle/classycle.gradle"
-
 dependencies {
     groovy libraries.groovy
 
@@ -10,6 +8,7 @@ dependencies {
     compile libraries.commons_io
     compile libraries.slf4j_api
     compile libraries.jna
+    compile libraries.nativePlatform
     compile module('org.jruby.ext.posix:jna-posix:1.0.3') {
         dependency libraries.jna
     }
@@ -20,11 +19,11 @@ dependencies {
     compile libraries.jcip
 }
 
-// TODO SF, replace with JavaVersion when 1.1 rc is building gradle
-if (!Jvm.current().isJava7()) {
+if (!javaVersion.java7) {
     sourceSets.main.java.exclude '**/jdk7/**'
     sourceSets.test.groovy.exclude '**/jdk7/**'
 }
 
 useTestFixtures()
+useClassycle()
 
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
deleted file mode 100644
index e1f8944..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NoOpTerminalDetector.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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/TerminalDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/TerminalDetector.java
deleted file mode 100644
index 719d875..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/TerminalDetector.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
deleted file mode 100755
index e9ba420..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/WindowsTerminalDetector.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.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/console/ConsoleDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/ConsoleDetector.java
new file mode 100644
index 0000000..5de70ab
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/ConsoleDetector.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.internal.nativeplatform.console;
+
+import org.gradle.api.Nullable;
+
+public interface ConsoleDetector {
+    /**
+     * Locates the console for this process, if any.
+     *
+     * @return Information about the console, or null if this process is not attached to the console.
+     */
+    @Nullable
+    ConsoleMetaData getConsole();
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/ConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/ConsoleMetaData.java
new file mode 100644
index 0000000..b6c54f7
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/ConsoleMetaData.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.internal.nativeplatform.console;
+
+public interface ConsoleMetaData {
+    /**
+     * Returns true if the current process' stdout is attached to the console.
+     */
+    boolean isStdOut();
+
+    /**
+     * Returns true if the current process' stderr is attached to the console.
+     */
+    boolean isStdErr();
+
+    /**
+     * <p>Returns the number of columns available in the console.</p>
+     *
+     * @return The number of columns available in the console. If no information is available return 0.
+     */
+    public int getCols();
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/FallbackConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/FallbackConsoleMetaData.java
new file mode 100644
index 0000000..d922297
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/FallbackConsoleMetaData.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.internal.nativeplatform.console;
+
+public class FallbackConsoleMetaData implements ConsoleMetaData{
+    public boolean isStdOut() {
+        return true;
+    }
+
+    public boolean isStdErr() {
+        return true;
+    }
+
+    public int getCols() {
+        return 0;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetector.java
new file mode 100644
index 0000000..b69d06a
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetector.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.internal.nativeplatform.console;
+
+import net.rubygrapefruit.platform.Terminals;
+
+import static net.rubygrapefruit.platform.Terminals.Output.Stderr;
+import static net.rubygrapefruit.platform.Terminals.Output.Stdout;
+
+public class NativePlatformConsoleDetector implements ConsoleDetector {
+    private final Terminals terminals;
+
+    public NativePlatformConsoleDetector(Terminals terminals) {
+        this.terminals = terminals;
+    }
+
+    public ConsoleMetaData getConsole() {
+        // Dumb terminal doesn't support ANSI control codes.
+        // TODO - remove this when we use Terminal rather than JAnsi to render to console
+        String term = System.getenv("TERM");
+        if (term != null && term.equals("dumb")) {
+            return null;
+        }
+
+        boolean stdout = terminals.isTerminal(Stdout);
+        boolean stderr = terminals.isTerminal(Stderr);
+        if (stdout) {
+            return new NativePlatformConsoleMetaData(stdout, stderr, terminals.getTerminal(Stdout));
+        } else if (stderr) {
+            return new NativePlatformConsoleMetaData(stdout, stderr, terminals.getTerminal(Stderr));
+        }
+        return null;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleMetaData.java
new file mode 100644
index 0000000..75ecf45
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NativePlatformConsoleMetaData.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.internal.nativeplatform.console;
+
+import net.rubygrapefruit.platform.Terminal;
+
+public class NativePlatformConsoleMetaData implements ConsoleMetaData {
+    private final boolean stdout;
+    private final boolean stderr;
+    private final Terminal terminal;
+
+    public NativePlatformConsoleMetaData(boolean stdout, boolean stderr, Terminal terminal) {
+        this.stdout = stdout;
+        this.stderr = stderr;
+        this.terminal = terminal;
+    }
+
+    public boolean isStdOut() {
+        return stdout;
+    }
+
+    public boolean isStdErr() {
+        return stderr;
+    }
+
+    public int getCols() {
+        return terminal.getTerminalSize().getCols();
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NoOpConsoleDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NoOpConsoleDetector.java
new file mode 100644
index 0000000..119300d
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/NoOpConsoleDetector.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.console;
+
+public class NoOpConsoleDetector implements ConsoleDetector {
+    public ConsoleMetaData getConsole() {
+        return null;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/UnixConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/UnixConsoleMetaData.java
new file mode 100644
index 0000000..9bfdcb1
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/UnixConsoleMetaData.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.internal.nativeplatform.console;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UnixConsoleMetaData implements ConsoleMetaData {
+    public static final Logger LOGGER = LoggerFactory.getLogger(UnixConsoleMetaData.class);
+    private final boolean stdout;
+    private final boolean stderr;
+
+    public UnixConsoleMetaData(boolean stdout, boolean stderr) {
+        this.stdout = stdout;
+        this.stderr = stderr;
+    }
+
+    public boolean isStdOut() {
+        return stdout;
+    }
+
+    public boolean isStdErr() {
+        return stderr;
+    }
+
+    public int getCols() {
+        final String columns = System.getenv("COLUMNS");
+        if (columns != null) {
+            try {
+                return Integer.parseInt(columns);
+            } catch (NumberFormatException ex) {
+                LOGGER.debug("Cannot parse COLUMNS environment variable to get console width. Value: '{}'", columns);
+            }
+        }
+        return 0;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/WindowsConsoleDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/WindowsConsoleDetector.java
new file mode 100755
index 0000000..7ef3716
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/console/WindowsConsoleDetector.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.internal.nativeplatform.console;
+
+import org.fusesource.jansi.WindowsAnsiOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class WindowsConsoleDetector implements ConsoleDetector {
+    public ConsoleMetaData getConsole() {
+        // Use Jansi's detection mechanism
+        try {
+            new WindowsAnsiOutputStream(new ByteArrayOutputStream());
+            return new FallbackConsoleMetaData();
+        } catch (IOException ignore) {
+            // Not attached to a console
+            return null;
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java
index bfa8069..66a7c68 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java
@@ -20,5 +20,14 @@ import java.io.File;
 import java.io.IOException;
 
 public interface Chmod {
-    public void chmod(File f, int mode) 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
+     */
+    public void chmod(File file, 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
index 448168f..6a4b851 100755
--- 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
@@ -21,7 +21,7 @@ import java.io.IOException;
 /**
  * A file system accessible to Gradle.
  */
-public interface FileSystem {
+public interface FileSystem extends Chmod {
     /**
      * Default Unix permissions for directories, {@code 755}.
      */
@@ -79,16 +79,4 @@ public interface FileSystem {
      * @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/FileSystemServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java
index f37472d..40e36d8 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java
@@ -57,8 +57,8 @@ public class FileSystemServices {
         LibC libC = loadLibC();
         serviceRegistry.add(Symlink.class, createSymlink(libC));
 
-        // Use libc backed implementations on Linux and Mac
-        if (operatingSystem.isLinux() || operatingSystem.isMacOsX()) {
+        // Use libc backed implementations on Linux and Mac, if libc available
+        if (libC != null && (operatingSystem.isLinux() || operatingSystem.isMacOsX())) {
             FilePathEncoder filePathEncoder = createEncoder(libC);
             serviceRegistry.add(Chmod.class, new LibcChmod(libC, filePathEncoder));
             serviceRegistry.add(Stat.class, new LibCStat(libC, operatingSystem, (BaseNativePOSIX) PosixUtil.current(), filePathEncoder));
@@ -80,7 +80,7 @@ public class FileSystemServices {
             }
         }
 
-        // Not windows, linux, mac or java 7. Attempt to use libc for chmod and posix for stat, and fallback to no-op implementations if not available
+        // Attempt to use libc for chmod and posix for stat, and fallback to no-op implementations if not available
         serviceRegistry.add(Chmod.class, createChmod(libC));
         serviceRegistry.add(Stat.class, createStat());
     }
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
index 036c43a..8b486f4 100644
--- 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
@@ -18,7 +18,6 @@ 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;
@@ -30,8 +29,6 @@ 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.
@@ -40,21 +37,18 @@ public class JnaBootPathConfigurer {
      *
      * @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");
+    public void configure(File storageDir) {
+        String nativePrefix = OperatingSystem.current().getNativePrefix();
+        File tmpDir = new File(storageDir, String.format("jna/%s", nativePrefix));
         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;
+            String resourceName = "/com/sun/jna/" + nativePrefix + "/" + jnaLibName;
             try {
                 InputStream lib = getClass().getResourceAsStream(resourceName);
                 if (lib == null) {
-                    throw new NativeIntegrationUnavailableException(String.format("Could not locate JNA native library resource '%s'.", resourceName));
+                    return;
                 }
                 try {
                     FileOutputStream outputStream = new FileOutputStream(libFile);
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedConsoleDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedConsoleDetector.java
new file mode 100644
index 0000000..6f8499c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedConsoleDetector.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.internal.nativeplatform.jna;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.nativeplatform.console.ConsoleMetaData;
+import org.gradle.internal.nativeplatform.console.UnixConsoleMetaData;
+import org.gradle.internal.nativeplatform.console.ConsoleDetector;
+
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+
+public class LibCBackedConsoleDetector implements ConsoleDetector {
+    private final LibC libC;
+
+    public LibCBackedConsoleDetector(LibC libC) {
+        this.libC = libC;
+    }
+
+    public ConsoleMetaData getConsole() {
+        boolean stdout = checkIsConsole(FileDescriptor.out);
+        boolean stderr = checkIsConsole(FileDescriptor.err);
+        if (!stdout && !stderr) {
+            return null;
+        }
+
+        // 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 null;
+        }
+
+        // Assume a terminal
+        return new UnixConsoleMetaData(stdout, stderr);
+    }
+
+    private boolean checkIsConsole(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
+        return libC.isatty(osFileDesc) != 0;
+    }
+}
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
deleted file mode 100644
index d15eab8..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedTerminalDetector.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.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/WindowsProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
index 87162dc..b2f77b8 100755
--- 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
@@ -29,7 +29,7 @@ public class WindowsProcessEnvironment extends AbstractProcessEnvironment {
 
     public void setNativeEnvironmentVariable(String name, String value) {
         boolean retval = kernel32.SetEnvironmentVariable(name, value == null ? null : value);
-        if (!retval) {
+        if (!retval && (kernel32.GetLastError() != 203/*ERROR_ENVVAR_NOT_FOUND*/)) {
             throw new NativeIntegrationException(String.format("Could not set environment variable '%s'. errno: %d", name, kernel32.GetLastError()));
         }
     }
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
index 9124feb..aef509a 100755
--- 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
@@ -16,20 +16,63 @@
 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 net.rubygrapefruit.platform.NativeException;
+import net.rubygrapefruit.platform.NativeIntegrationUnavailableException;
+import net.rubygrapefruit.platform.Terminals;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.nativeplatform.*;
+import org.gradle.internal.nativeplatform.console.ConsoleDetector;
+import org.gradle.internal.nativeplatform.console.NativePlatformConsoleDetector;
+import org.gradle.internal.nativeplatform.console.NoOpConsoleDetector;
+import org.gradle.internal.nativeplatform.console.WindowsConsoleDetector;
 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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
 
 /**
  * Provides various native platform integration services.
  */
 public class NativeServices extends DefaultServiceRegistry {
+    private static final Logger LOGGER = LoggerFactory.getLogger(NativeServices.class);
+    private static final boolean USE_NATIVE_PLATFORM = "true".equalsIgnoreCase(System.getProperty("org.gradle.native", "true"));
+    private static final NativeServices INSTANCE = new NativeServices();
+
+    /**
+     * Initializes the native services to use the given user home directory to store native libs and other resources. Does nothing if already initialized. Will be implicitly initialized on first usage
+     * of a native service. Also initializes the Native-Platform library using the passed user home directory.
+     */
+    public static void initialize(File userHomeDir) {
+        File nativeDir = new File(userHomeDir, "native");
+        if (USE_NATIVE_PLATFORM) {
+            try {
+                net.rubygrapefruit.platform.Native.init(nativeDir);
+            } catch (NativeIntegrationUnavailableException ex) {
+                LOGGER.debug("Native-platform is not available.");
+            } catch (NativeException ex) {
+                LOGGER.debug("Unable to initialize native-platform. Failure: {}", format(ex));
+            }
+        }
+        new JnaBootPathConfigurer().configure(nativeDir);
+    }
+
+    public static NativeServices getInstance() {
+        return INSTANCE;
+    }
+
+    private NativeServices() {
+    }
+
+    @Override
+    public void close() {
+        // Don't close
+    }
+
     protected OperatingSystem createOperatingSystem() {
         return OperatingSystem.current();
     }
@@ -39,33 +82,58 @@ public class NativeServices extends DefaultServiceRegistry {
     }
 
     protected ProcessEnvironment createProcessEnvironment() {
+        OperatingSystem operatingSystem = get(OperatingSystem.class);
         try {
-            if (OperatingSystem.current().isUnix()) {
+            if (operatingSystem.isUnix()) {
                 return new LibCBackedProcessEnvironment(get(LibC.class));
-            } else if (OperatingSystem.current().isWindows()) {
+            } else if (operatingSystem.isWindows()) {
                 return new WindowsProcessEnvironment();
             } else {
                 return new UnsupportedEnvironment();
             }
         } catch (LinkageError e) {
             // Thrown when jna cannot initialize the native stuff
+            LOGGER.debug("Unable to load native library. Continuing with fallback. Failure: {}", format(e));
             return new UnsupportedEnvironment();
         }
     }
 
-    protected TerminalDetector createTerminalDetector() {
+    protected ConsoleDetector createConsoleDetector() {
+        OperatingSystem operatingSystem = get(OperatingSystem.class);
+        if (USE_NATIVE_PLATFORM) {
+            try {
+                Terminals terminals = net.rubygrapefruit.platform.Native.get(Terminals.class);
+                return new NativePlatformConsoleDetector(terminals);
+            } catch (NativeIntegrationUnavailableException ex) {
+                LOGGER.debug("Native-platform terminal is not available. Continuing with fallback.");
+            } catch (NativeException ex) {
+                LOGGER.debug("Unable to load from native-platform backed ConsoleDetector. Continuing with fallback. Failure: {}", format(ex));
+            }
+        }
         try {
-            if (get(OperatingSystem.class).isWindows()) {
-                return new WindowsTerminalDetector();
+            if (operatingSystem.isWindows()) {
+                return new WindowsConsoleDetector();
             }
-            return new LibCBackedTerminalDetector(get(LibC.class));
+            return new LibCBackedConsoleDetector(get(LibC.class));
         } catch (LinkageError e) {
             // Thrown when jna cannot initialize the native stuff
-            return new NoOpTerminalDetector();
+            LOGGER.debug("Unable to load native library. Continuing with fallback. Failure: {}", format(e));
+            return new NoOpConsoleDetector();
         }
     }
-    
+
     protected LibC createLibC() {
         return (LibC) Native.loadLibrary("c", LibC.class);
     }
+
+    private static String format(Throwable throwable) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(throwable.toString());
+        for (Throwable current = throwable.getCause(); current != null; current = current.getCause()) {
+            builder.append(SystemProperties.getLineSeparator());
+            builder.append("caused by: ");
+            builder.append(current.toString());
+        }
+        return builder.toString();
+    }
 }
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetectorTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetectorTest.groovy
new file mode 100644
index 0000000..d21b6a6
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/console/NativePlatformConsoleDetectorTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.console
+
+import spock.lang.Specification
+import net.rubygrapefruit.platform.Terminals
+
+class NativePlatformConsoleDetectorTest extends Specification {
+    private Terminals terminals = Mock()
+    private NativePlatformConsoleDetector detector = new NativePlatformConsoleDetector(terminals)
+
+    def "returns null when neither stdout or stderr is attached to console"() {
+        given:
+        terminals.isTerminal(Terminals.Output.Stdout) >> false
+        terminals.isTerminal(Terminals.Output.Stderr) >> false
+
+        expect:
+        detector.console == null
+    }
+
+    def "returns metadata when stdout and stderr are attached to console"() {
+        given:
+        terminals.isTerminal(Terminals.Output.Stdout) >> true
+        terminals.isTerminal(Terminals.Output.Stderr) >> true
+
+        expect:
+        detector.console != null
+        detector.console.stdOut
+        detector.console.stdErr
+    }
+
+    def "returns metadata when only stdout is attached to console"() {
+        given:
+        terminals.isTerminal(Terminals.Output.Stdout) >> true
+        terminals.isTerminal(Terminals.Output.Stderr) >> false
+
+        expect:
+        detector.console != null
+        detector.console.stdOut
+        !detector.console.stdErr
+    }
+
+    def "returns metadata when only stderr is attached to console"() {
+        given:
+        terminals.isTerminal(Terminals.Output.Stdout) >> false
+        terminals.isTerminal(Terminals.Output.Stderr) >> true
+
+        expect:
+        detector.console != null
+        !detector.console.stdOut
+        detector.console.stdErr
+    }
+}
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
index 69abfdd..b643fcb 100644
--- 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
@@ -24,7 +24,7 @@ import org.gradle.util.TestPrecondition
 
 class LibCBackedProcessEnvironmentTest extends Specification {
 
-    NativeServices registry = new NativeServices();
+    NativeServices registry = NativeServices.getInstance();
 
     @Requires(TestPrecondition.FILE_PERMISSIONS) //MACOSX & UNIX
     def "setNativeEnvironmentVariable throws NativeEnvironmentException for NULL value on env name with errno code"() {
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
index ec7968d..8941b5c 100644
--- 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
@@ -27,7 +27,7 @@ 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)
+    final ProcessEnvironment env = NativeServices.getInstance().get(ProcessEnvironment)
 
     @Requires(TestPrecondition.SET_ENV_VARIABLE)
     def "can set and remove environment variable"() {
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
index 93dd007..829c172 100755
--- 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
@@ -15,15 +15,15 @@
  */
 package org.gradle.internal.nativeplatform.services
 
+import org.gradle.internal.nativeplatform.console.ConsoleDetector
 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()
-    
+    final NativeServices services = NativeServices.getInstance()
+
     def "makes a ProcessEnvironment available"() {
         expect:
         services.get(ProcessEnvironment) != null
@@ -39,8 +39,8 @@ class NativeServicesTest extends Specification {
         services.get(FileSystem) != null
     }
 
-    def "makes a TerminalDetector available"() {
+    def "makes a ConsoleDetector available"() {
         expect:
-        services.get(TerminalDetector) != null
+        services.get(ConsoleDetector) != null
     }
 }
diff --git a/subprojects/open-api/open-api.gradle b/subprojects/open-api/open-api.gradle
index 39be9ad..154ff39 100644
--- a/subprojects/open-api/open-api.gradle
+++ b/subprojects/open-api/open-api.gradle
@@ -9,4 +9,7 @@ useTestFixtures()
 
 integTestTasks.all {
     jvmArgs '-XX:MaxPermSize=256m'
+    if (isWindows && systemProperties['org.gradle.integtest.executer'] == "embedded") {
+        systemProperties['org.gradle.integtest.executer'] =  "forking"
+    }
 }
\ No newline at end of file
diff --git a/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy b/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy
index 672fde0..0c2f044 100644
--- a/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy
+++ b/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy
@@ -26,6 +26,7 @@ class OsgiPluginIntegrationSpec extends AbstractIntegrationSpec {
         given:
         buildFile << """
             version = "1.0"
+            group = "foo"
             apply plugin: "java"
             apply plugin: "osgi"
                             
@@ -33,8 +34,11 @@ class OsgiPluginIntegrationSpec extends AbstractIntegrationSpec {
                 manifest {
                     version = "3.0"
                     instructionReplace("Bundle-Version", "2.0")
+                    instructionReplace("Bundle-SymbolicName", "bar")
                 }
             }
+
+            assert jar.manifest.symbolicName.startsWith("bar") // GRADLE-2446
         """
         
         and:
@@ -43,8 +47,10 @@ class OsgiPluginIntegrationSpec extends AbstractIntegrationSpec {
         when:
         run "jar"
 
+        def manifestText = file("build/tmp/jar/MANIFEST.MF").text
         then:
-        file("build/tmp/jar/MANIFEST.MF").text.contains("Bundle-Version: 2.0")
+        manifestText.contains("Bundle-Version: 2.0")
+        manifestText.contains("Bundle-SymbolicName: bar")
     }
 
     @Issue("http://issues.gradle.org/browse/GRADLE-2237")
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 2aa4750..4f01491 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
@@ -21,9 +21,10 @@ 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.api.specs.Spec;
 import org.gradle.internal.Factory;
 import org.gradle.internal.UncheckedException;
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.WrapUtil;
 
 import java.io.File;
@@ -35,11 +36,27 @@ import java.util.jar.Manifest;
  * @author Hans Dockter
  */
 public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest {
+
+    // Because these properties can be convention mapped we need special handling in here.
+    // If you add another one of these “modelled” properties, you need to update:
+    // - maybeAppendModelledInstruction()
+    // - maybePrependModelledInstruction()
+    // - maybeSetModelledInstruction()
+    // - getModelledInstructions()
+    // - instructionValue()
+    private String symbolicName;
+    private String name;
+    private String version;
+    private String description;
+    private String license;
+    private String vendor;
+    private String docURL;
+
     private File classesDir;
 
     private Factory<ContainedVersionAnalyzer> analyzerFactory = new DefaultAnalyzerFactory();
 
-    private Map<String, List<String>> instructions = new HashMap<String, List<String>>();
+    private Map<String, List<String>> unmodelledInstructions = new HashMap<String, List<String>>();
 
     private FileCollection classpath;
 
@@ -81,6 +98,9 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
                 analyzer.setProperty(key, attribute.getValue().toString());
             }
         }
+
+
+        Map<String, List<String>> instructions = getInstructions();
         Set<String> instructionNames = instructions.keySet();
         if (!instructionNames.contains(Analyzer.IMPORT_PACKAGE)) {
             analyzer.setProperty(Analyzer.IMPORT_PACKAGE,
@@ -120,102 +140,254 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
     }
 
     public List<String> instructionValue(String instructionName) {
-        return instructions.get(instructionName);
+        if (instructionName.equals(Analyzer.BUNDLE_SYMBOLICNAME)) {
+            return createListFromPropertyString(getSymbolicName());
+        } else if (instructionName.equals(Analyzer.BUNDLE_NAME)) {
+            return createListFromPropertyString(getName());
+        } else if (instructionName.equals(Analyzer.BUNDLE_VERSION)) {
+            return createListFromPropertyString(getVersion());
+        } else if (instructionName.equals(Analyzer.BUNDLE_DESCRIPTION)) {
+            return createListFromPropertyString(getDescription());
+        } else if (instructionName.equals(Analyzer.BUNDLE_LICENSE)) {
+            return createListFromPropertyString(getLicense());
+        } else if (instructionName.equals(Analyzer.BUNDLE_VENDOR)) {
+            return createListFromPropertyString(getVendor());
+        } else if (instructionName.equals(Analyzer.BUNDLE_DOCURL)) {
+            return createListFromPropertyString(getDocURL());
+        } else {
+            return unmodelledInstructions.get(instructionName);
+        }
     }
 
     public OsgiManifest instruction(String name, String... values) {
-        if (instructions.get(name) == null) {
-            instructions.put(name, new ArrayList<String>());
+        if (!maybeAppendModelledInstruction(name, values)) {
+            if (unmodelledInstructions.get(name) == null) {
+                unmodelledInstructions.put(name, new ArrayList<String>());
+            }
+            unmodelledInstructions.get(name).addAll(Arrays.asList(values));
         }
-        instructions.get(name).addAll(Arrays.asList(values));
+
         return this;
     }
 
+    private String appendValues(String existingValues, String... toPrepend) {
+        List<String> parts = createListFromPropertyString(existingValues);
+        if (parts == null) {
+            return createPropertyStringFromArray(toPrepend);
+        } else {
+            parts.addAll(Arrays.asList(toPrepend));
+            return createPropertyStringFromList(parts);
+        }
+    }
+
+    private boolean maybeAppendModelledInstruction(String name, String... values) {
+        if (name.equals(Analyzer.BUNDLE_SYMBOLICNAME)) {
+            setSymbolicName(appendValues(getSymbolicName(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_NAME)) {
+            setName(appendValues(getName(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VERSION)) {
+            setVersion(appendValues(getVersion(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DESCRIPTION)) {
+            setDescription(appendValues(getDescription(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_LICENSE)) {
+            setLicense(appendValues(getLicense(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VENDOR)) {
+            setVendor(appendValues(getVendor(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DOCURL)) {
+            setDocURL(appendValues(getDocURL(), values));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public OsgiManifest instructionFirst(String name, String... values) {
-        if (instructions.get(name) == null) {
-            instructions.put(name, new ArrayList<String>());
+        if (!maybePrependModelledInstruction(name, values)) {
+            if (unmodelledInstructions.get(name) == null) {
+                unmodelledInstructions.put(name, new ArrayList<String>());
+            }
+            unmodelledInstructions.get(name).addAll(0, Arrays.asList(values));
         }
-        instructions.get(name).addAll(0, Arrays.asList(values));
         return this;
     }
 
-    public OsgiManifest instructionReplace(String name, String... values) {
-        if (values.length == 0 || (values.length == 1 && values[0] == null)) {
-            instructions.remove(name);
+    private String prependValues(String existingValues, String... toPrepend) {
+        List<String> parts = createListFromPropertyString(existingValues);
+        if (parts == null) {
+            return createPropertyStringFromArray(toPrepend);
+        } else {
+            parts.addAll(0, Arrays.asList(toPrepend));
+            return createPropertyStringFromList(parts);
+        }
+    }
+
+    private boolean maybePrependModelledInstruction(String name, String... values) {
+        if (name.equals(Analyzer.BUNDLE_SYMBOLICNAME)) {
+            setSymbolicName(prependValues(getSymbolicName(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_NAME)) {
+            setName(prependValues(getName(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VERSION)) {
+            setVersion(prependValues(getVersion(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DESCRIPTION)) {
+            setDescription(prependValues(getDescription(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_LICENSE)) {
+            setLicense(prependValues(getLicense(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VENDOR)) {
+            setVendor(prependValues(getVendor(), values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DOCURL)) {
+            setDocURL(prependValues(getDocURL(), values));
+            return true;
         } else {
-            if (instructions.get(name) == null) {
-                instructions.put(name, new ArrayList<String>());
+            return false;
+        }
+    }
+
+    public OsgiManifest instructionReplace(String name, String... values) {
+        if (!maybeSetModelledInstruction(name, values)) {
+            if (values.length == 0 || (values.length == 1 && values[0] == null)) {
+                unmodelledInstructions.remove(name);
+            } else {
+                if (unmodelledInstructions.get(name) == null) {
+                    unmodelledInstructions.put(name, new ArrayList<String>());
+                }
+                List<String> instructionsForName = unmodelledInstructions.get(name);
+                instructionsForName.clear();
+                Collections.addAll(instructionsForName, values);
             }
-            List<String> instructionsForName = instructions.get(name);
-            instructionsForName.clear();
-            Collections.addAll(instructionsForName, values);
         }
 
         return this;
     }
 
+    private boolean maybeSetModelledInstruction(String name, String... values) {
+        if (name.equals(Analyzer.BUNDLE_SYMBOLICNAME)) {
+            setSymbolicName(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_NAME)) {
+            setName(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VERSION)) {
+            setVersion(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DESCRIPTION)) {
+            setDescription(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_LICENSE)) {
+            setLicense(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_VENDOR)) {
+            setVendor(createPropertyStringFromArray(values));
+            return true;
+        } else if (name.equals(Analyzer.BUNDLE_DOCURL)) {
+            setDocURL(createPropertyStringFromArray(values));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public Map<String, List<String>> getInstructions() {
+        Map<String, List<String>> instructions = new HashMap<String, List<String>>();
+        instructions.putAll(unmodelledInstructions);
+        instructions.putAll(getModelledInstructions());
         return instructions;
     }
 
+    private String createPropertyStringFromArray(String... valueList) {
+        return createPropertyStringFromList(Arrays.asList(valueList));
+    }
+
     private String createPropertyStringFromList(List<String> valueList) {
-        return valueList == null || valueList.isEmpty() ? null : GUtil.join(valueList, ",");
+        return valueList == null || valueList.isEmpty() ? null : CollectionUtils.join(",", valueList);
+    }
+
+    private List<String> createListFromPropertyString(String propertyString) {
+        return propertyString == null || propertyString.length() == 0 ? null : new LinkedList<String>(Arrays.asList(propertyString.split(",")));
+    }
+
+    private Map<String, List<String>> getModelledInstructions() {
+        Map<String, List<String>> modelledInstructions = new HashMap<String, List<String>>();
+        modelledInstructions.put(Analyzer.BUNDLE_SYMBOLICNAME, createListFromPropertyString(symbolicName));
+        modelledInstructions.put(Analyzer.BUNDLE_NAME, createListFromPropertyString(name));
+        modelledInstructions.put(Analyzer.BUNDLE_VERSION, createListFromPropertyString(version));
+        modelledInstructions.put(Analyzer.BUNDLE_DESCRIPTION, createListFromPropertyString(description));
+        modelledInstructions.put(Analyzer.BUNDLE_LICENSE, createListFromPropertyString(description));
+        modelledInstructions.put(Analyzer.BUNDLE_VENDOR, createListFromPropertyString(vendor));
+        modelledInstructions.put(Analyzer.BUNDLE_DOCURL, createListFromPropertyString(docURL));
+
+        return CollectionUtils.filter(modelledInstructions, new Spec<Map.Entry<String, List<String>>>() {
+            public boolean isSatisfiedBy(Map.Entry<String, List<String>> element) {
+                return element.getValue() != null;
+            }
+        });
     }
 
     public String getSymbolicName() {
-        return instructionValueString(Analyzer.BUNDLE_SYMBOLICNAME);
+        return symbolicName;
     }
 
     public void setSymbolicName(String symbolicName) {
-        instructionReplace(Analyzer.BUNDLE_SYMBOLICNAME, symbolicName);
+        this.symbolicName = symbolicName;
     }
 
     public String getName() {
-        return instructionValueString(Analyzer.BUNDLE_NAME);
+        return name;
     }
 
     public void setName(String name) {
-        instructionReplace(Analyzer.BUNDLE_NAME, name);
+        this.name = name;
     }
 
     public String getVersion() {
-        return instructionValueString(Analyzer.BUNDLE_VERSION);
+        return version;
     }
 
     public void setVersion(String version) {
-        instructionReplace(Analyzer.BUNDLE_VERSION, version);
+        this.version = version;
     }
 
     public String getDescription() {
-        return instructionValueString(Analyzer.BUNDLE_DESCRIPTION);
+        return description;
     }
 
     public void setDescription(String description) {
-        instructionReplace(Analyzer.BUNDLE_DESCRIPTION, description);
+        this.description = description;
     }
 
     public String getLicense() {
-        return instructionValueString(Analyzer.BUNDLE_LICENSE);
+        return license;
     }
 
     public void setLicense(String license) {
-        instructionReplace(Analyzer.BUNDLE_LICENSE, license);
+        this.license = license;
     }
 
     public String getVendor() {
-        return instructionValueString(Analyzer.BUNDLE_VENDOR);
+        return vendor;
     }
 
     public void setVendor(String vendor) {
-        instructionReplace(Analyzer.BUNDLE_VENDOR, vendor);
+        this.vendor = vendor;
     }
 
     public String getDocURL() {
-        return instructionValueString(Analyzer.BUNDLE_DOCURL);
+        return docURL;
     }
 
     public void setDocURL(String docURL) {
-        instructionReplace(Analyzer.BUNDLE_DOCURL, docURL);
+        this.docURL = docURL;
     }
 
     public File getClassesDir() {
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
index 90ac01a..59f5483 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
@@ -32,7 +32,7 @@ class OsgiPluginConventionTest extends Specification {
     OsgiPluginConvention osgiPluginConvention = new OsgiPluginConvention(project)
 
     def setup() {
-        new JavaBasePlugin().apply(project)
+        project.plugins.apply(JavaBasePlugin)
     }
 
     def osgiManifestWithNoClosure() {
@@ -68,14 +68,24 @@ class OsgiPluginConventionTest extends Specification {
     @Issue("GRADLE-1670")
     def "computes its defaults lazily"() {
         def manifest = osgiPluginConvention.osgiManifest()
-        project.version = 2.1
+        def i = 0
+        project.version = "${->++i}"
         project.group = "my.group"
         project.archivesBaseName = "myarchive"
 
         expect:
-        manifest.version == "2.1"
+        manifest.version == "1"
+        manifest.version == "2"
         manifest.name == "myarchive"
         manifest.symbolicName == "my.group.myarchive"
+
+        when:
+        project.group = "changed.group"
+        project.archivesBaseName = "changedarchive"
+
+        then:
+        manifest.name == "changedarchive"
+        manifest.symbolicName == "changed.group.changedarchive"
     }
 
     void matchesExpectedConfig(DefaultOsgiManifest osgiManifest) {
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index f55f7fb..d48a856 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -7,6 +7,7 @@ configurations {
 dependencies {
     junit 'junit:junit:4.10'
     groovy libraries.groovy
+    testFixturesCompile project(':internalIntegTesting')
 }
 
 useTestFixtures()
@@ -17,6 +18,7 @@ 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
+    withPlainAntCompile = true
 }
 
 task multi(type: ProjectGeneratorTask, description: 'Generates a multi-project build') {
@@ -24,24 +26,49 @@ task multi(type: ProjectGeneratorTask, description: 'Generates a multi-project b
     sourceFiles = 100
 }
 
+task parallel(type: ProjectGeneratorTask, description: 'Generates a multi-project build') {
+    projects = 8
+    sourceFiles = 1000
+}
+
 task mixedSize(type: ProjectGeneratorTask) {
     projects = 400
     sourceFiles = 100
     projects[1].sourceFiles = 20000
 }
 
+task withTestNG(type: ProjectGeneratorTask) {
+    projects = 2
+    sourceFiles = 10
+    testReport = false
+    testFramework = 'useTestNG()'
+    testClassTemplate = 'TestNGTest.java'
+}
+
+task withVerboseJUnits(type: ProjectGeneratorTask) {
+    projects = 2
+    sourceFiles = 10
+    testReport = true
+    testClassTemplate = 'VerboseJUnitTest.java'
+}
+
 task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-project groovy build') {
     projects = 25
     groovyProject = true
 }
 
+task multiScala(type: ProjectGeneratorTask, description: 'Generates a multi-project Scala build') {
+    projects = 25
+    scalaProject = true
+}
+
 task largeMulti(type: ProjectGeneratorTask, description: 'Generates a large multi-project build') {
     projects = 800
     sourceFiles = 100
 }
 
 task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small multi-project build with a large Dependency Graph'){
-    projects = 25
+    projects = 5
     sourceFiles = 100
 
     dependencyGraph {
@@ -58,21 +85,32 @@ generators.all {
 }
 task all(dependsOn: generators)
 
-task prepareSamples(dependsOn: [small, multi])
+task prepareSamples(dependsOn: [small, multi, lotDependencies, withTestNG, withVerboseJUnits])
 
-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')"
+integTestTasks.all {
+    if (buildTypes.isActive('performanceTest')) {
+        dependsOn prepareSamples
+    } else {
+        enabled = false
+        dependsOn = []
+    }
+    maxParallelForks = 1
 }
 
-tasks.integTest.testLogging.showStandardStreams = true
+tasks.integTest.testLogging {
+    showStandardStreams = true
+    lifecycle {
+        exceptionFormat 'full'
+    }
+}
 
-gradle.taskGraph.whenReady {
-    if (!it.hasTask(':performance:performanceTest')) {
-        project.tasks.withType(Test) {
-            logger.info("Skipping $it because task 'performanceTest' was not requested.")
-            enabled = false
+eclipse {
+    classpath {
+        file.whenMerged { classpath ->
+            //**TODO
+            classpath.entries.removeAll {it.path.contains('src/test/groovy')}
+            classpath.entries.removeAll {it.path.contains('src/testFixtures/groovy')}
+            classpath.entries.removeAll {it.path.contains('src/integTest/groovy')}
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/performance/src/generator.groovy b/subprojects/performance/src/generator.groovy
index 1096867..5b9885d 100644
--- a/subprojects/performance/src/generator.groovy
+++ b/subprojects/performance/src/generator.groovy
@@ -37,10 +37,15 @@ class ProjectGeneratorTask extends DefaultTask {
     @OutputDirectory
     File destDir
     boolean groovyProject
+    boolean scalaProject
+    boolean withPlainAntCompile
     int sourceFiles = 1
     Integer testSourceFiles
     int linesOfCodePerSourceFile = 5
     @InputFiles FileCollection testDependencies
+    String testClassTemplate = 'Test.java'
+    boolean testReport = false
+    String testFramework = 'useJUnit()'
 
     final List<TestProject> projects = []
     final SimpleTemplateEngine engine = new SimpleTemplateEngine()
@@ -147,7 +152,10 @@ class ProjectGeneratorTask extends DefaultTask {
             }
         }
 
-        args += [projectName: testProject.name, groovyProject: groovyProject, propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7)), repository: testProject.repository, dependencies:testProject.dependencies]
+        args += [projectName: testProject.name, groovyProject: groovyProject, scalaProject: scalaProject, withPlainAntCompile: withPlainAntCompile,
+                propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7)), repository: testProject.repository, dependencies:testProject.dependencies,
+                testProject: testProject
+                ]
 
         files.each {String name ->
             generate(name, name, args)
@@ -162,7 +170,7 @@ class ProjectGeneratorTask extends DefaultTask {
             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)
+                generate("src/test/java/${packageName.replace('.', '/')}/${classArgs.testClassName}.java", testProject.defaults.testClassTemplate, classArgs)
             }
             if (groovyProject) {
                 testProject.sourceFiles.times {
@@ -176,6 +184,18 @@ class ProjectGeneratorTask extends DefaultTask {
                     generate("src/test/groovy/${packageName.replace('.', '/')}/${classArgs.testClassName}.groovy", 'Test.groovy', classArgs)
                 }
             }
+            if (scalaProject) {
+                testProject.sourceFiles.times {
+                    String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                    Map classArgs = args + [packageName: packageName, productionClassName: "ProductionScala${it + 1}"]
+                    generate("src/main/scala/${packageName.replace('.', '/')}/${classArgs.productionClassName}.scala", 'Production.scala', classArgs)
+                }
+                testProject.testSourceFiles.times {
+                    String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                    Map classArgs = args + [packageName: packageName, productionClassName: "ProductionScala${it + 1}", testClassName: "TestScala${it + 1}"]
+                    generate("src/test/scala/${packageName.replace('.', '/')}/${classArgs.testClassName}.scala", 'Test.scala', classArgs)
+                }
+            }
         }
     }
 
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy
new file mode 100644
index 0000000..9fee778
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/DependencyResolutionStressTest.groovy
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.GradleDistributionExecuter
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.junit.Rule
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.HttpHeaders
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.bio.SocketConnector
+import org.mortbay.jetty.handler.AbstractHandler
+import spock.lang.Specification
+
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+class DependencyResolutionStressTest extends Specification {
+    @Rule GradleDistribution distribution = new GradleDistribution()
+    @Rule StressHttpServer server = new StressHttpServer()
+    @Rule ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
+
+    def setup() {
+        distribution.requireOwnUserHomeDir()
+        concurrent.shortTimeout = 180000
+    }
+
+    def "handles concurrent access to changing artifacts"() {
+        expect:
+        4.times { count ->
+            concurrent.start {
+                def buildDir = distribution.file(count)
+                buildDir.file('build.gradle') << """
+import java.util.zip.*
+
+repositories {
+    ivy { url '${server.uri}' }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile 'org.gradle:changing:1.0'
+}
+
+task check << {
+    def file = configurations.compile.singleFile
+    println "THREAD $count -> checking \$file.name size: \${file.length()}"
+    file.withInputStream { instr ->
+        def zipStream = new ZipInputStream(instr)
+        def entries = []
+        for (ZipEntry entry = zipStream.nextEntry; entry != null; entry = zipStream.nextEntry) {
+            entries << entry.name
+        }
+        assert entries == ['a', 'b']
+    }
+}
+        """
+
+                GradleDistributionExecuter executer = distribution.executer()
+                executer.withForkingExecuter()
+                10.times {
+                    executer.inDirectory(buildDir).withArgument("--refresh-dependencies").withTasks('check').run()
+                }
+            }
+        }
+        concurrent.finished()
+    }
+
+    static class StressHttpServer extends ExternalResource {
+        final Server server = new Server(0)
+        final Resources resources = new Resources()
+
+        @Override
+        protected void before() {
+            server.addConnector(new SocketConnector())
+            server.addHandler(new AbstractHandler() {
+                void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                    println "* Handling $request.method $request.pathInfo"
+                    if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
+                        handleGetIvy(request, response)
+                        request.handled = true
+                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/ivy-1.0.xml') {
+                        handleHeadIvy(request, response)
+                        request.handled = true
+                    } else if (request.method == 'GET' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
+                        handleGetJar(request, response)
+                        request.handled = true
+                    } else if (request.method == 'HEAD' && request.pathInfo == '/org.gradle/changing/1.0/changing-1.0.jar') {
+                        handleHeadJar(request, response)
+                        request.handled = true
+                    }
+                }
+            })
+            server.start()
+        }
+
+        private void handleGetIvy(HttpServletRequest request, HttpServletResponse response) {
+            println "* GET IVY FILE"
+            def ivy = resources.ivy
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
+            response.setContentLength(ivy.contentLength)
+            response.setContentType(ivy.contentType)
+            ivy.writeContentTo(response.outputStream)
+        }
+
+        private void handleHeadIvy(HttpServletRequest request, HttpServletResponse response) {
+            println "* HEAD IVY FILE"
+            def ivy = resources.ivy
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, ivy.lastModified)
+            response.setContentLength(ivy.contentLength)
+            response.setContentType(ivy.contentType)
+        }
+
+        private void handleGetJar(HttpServletRequest request, HttpServletResponse response) {
+            println "* GET JAR"
+            def jar = resources.jar
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
+            response.setContentLength(jar.contentLength)
+            response.setContentType(jar.contentType)
+            jar.writeContentTo(response.outputStream)
+        }
+
+        private void handleHeadJar(HttpServletRequest request, HttpServletResponse response) {
+            println "* HEAD JAR"
+            def jar = resources.jar
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, jar.lastModified)
+            response.setContentLength(jar.contentLength)
+            response.setContentType(jar.contentType)
+        }
+
+        @Override
+        protected void after() {
+            server.stop()
+        }
+
+        URI getUri() {
+            return new URI("http://localhost:${server.connectors[0].localPort}/")
+        }
+    }
+
+    static class Resources {
+        private final Object lock = new Object()
+        private int count
+        private Resource ivy
+        private Resource jar
+        private final IvyFileGenerator ivyGenerator = new IvyFileGenerator()
+        private final JarFileGenerator jarGenerator = new JarFileGenerator()
+
+        Resource getIvy() {
+            synchronized (lock) {
+                maybeReset()
+                if (ivy == null) {
+                    ivy = ivyGenerator.regenerate()
+                }
+                return ivy
+            }
+        }
+
+        Resource getJar() {
+            synchronized (lock) {
+                maybeReset()
+                if (jar == null) {
+                    jar = jarGenerator.regenerate()
+                }
+                return jar
+            }
+        }
+
+        private void maybeReset() {
+            count++
+            if (count > 4) {
+                println "*** RESET"
+                count = 0
+                ivy = null
+                jar = null
+            }
+        }
+    }
+
+    static class Resource {
+        final long lastModified
+        final byte[] content
+        final String contentType
+
+        Resource(byte[] content, long lastModified, String contentType) {
+            this.content = content
+            this.lastModified = lastModified
+            this.contentType = contentType
+        }
+
+        int getContentLength() {
+            content.length
+        }
+
+        void writeContentTo(OutputStream outputStream) {
+            outputStream.write(content)
+        }
+    }
+
+    static abstract class ResourceGenerator {
+        private final Random random = new Random();
+
+        Resource regenerate() {
+            def str = new ByteArrayOutputStream()
+            generateContent(str)
+            def content = str.toByteArray()
+            def lastModified = System.currentTimeMillis()
+            return new Resource(content, lastModified, contentType)
+        }
+
+        // Writes a long string of text encoded with the system encoding to the given output stream
+        protected void writeLongString(OutputStream outputStream) {
+            for (int i = 0; i < 25000; i++) {
+                outputStream.write(String.valueOf(random.nextInt()).bytes)
+            }
+        }
+
+        protected abstract String getContentType()
+
+        protected abstract void generateContent(OutputStream outputStream)
+    }
+
+    static class IvyFileGenerator extends ResourceGenerator {
+        @Override
+        String getContentType() {
+            return "text/xml"
+        }
+
+        @Override
+        void generateContent(OutputStream outputStream) {
+            outputStream << '''
+<ivy-module version="1.0">
+    <info organisation="org.gradle" module="changing" revision="1.0"/>
+    <!--
+'''
+            writeLongString(outputStream)
+            outputStream << '''
+-->
+</ivy-module>
+'''
+        }
+    }
+
+    static class JarFileGenerator extends ResourceGenerator {
+        @Override
+        String getContentType() {
+            return "application/java-archive"
+        }
+
+        @Override
+        void generateContent(OutputStream outputStream) {
+            def zipStream = new ZipOutputStream(outputStream)
+            zipStream.putNextEntry(new ZipEntry("a"))
+            writeLongString(zipStream)
+            zipStream.putNextEntry(new ZipEntry("b"))
+            writeLongString(zipStream)
+            zipStream.finish()
+        }
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
index 2a2d721..429a0b2 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
@@ -16,41 +16,129 @@
 
 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
 
+import static org.gradle.peformance.fixture.DataAmount.*
+import static org.gradle.peformance.fixture.Duration.*
+
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
 class PerformanceTest extends Specification {
+    @Unroll("Project '#testProject' clean build")
+    def "clean build"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['clean', 'build'],
+                otherVersions: ['1.0'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | runs | maxExecutionTimeRegression
+        "small"           | 5    | millis(500)
+        "multi"           | 5    | millis(1000)
+        "lotDependencies" | 5    | millis(1000)
+    }
 
-    def current = new GradleDistribution()
-    def previous = new ReleasedVersions(current).last
+    @Unroll("Project '#testProject' up-to-date build")
+    def "build"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['build'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
 
-    @Unroll("Project '#testProject' ran #runs times. Current release is not slower than the previous one.")
-    def "speed"() {
+        where:
+        testProject       | runs | maxExecutionTimeRegression
+        "small"           | 5    | millis(500)
+        "multi"           | 5    | millis(1000)
+        "lotDependencies" | 5    | millis(1000)
+    }
+
+    @Unroll("Project '#testProject' dependency report")
+    def "dependency report"() {
         expect:
-        def result = new PerformanceTestRunner(testProject: testProject, runs: runs, warmUpRuns: 1, accuracyMs: accuracyMs).run()
-        result.assertCurrentReleaseIsNotSlower()
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['dependencyReport'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
 
         where:
-        testProject | runs | accuracyMs
-        "small"     | 10   | 500
-        "multi"     | 10   | 1000
+        testProject       | runs | maxExecutionTimeRegression
+        "lotDependencies" | 5    | millis(1000)
     }
 
-    @Unroll("Project '#testProject' with heap size: #heapSize. Current release does not require more memory than the previous one.")
-    def "memory"() {
+    @Unroll("Project '#testProject' eclipse")
+    def "eclipse"() {
         expect:
-        def result = new PerformanceTestRunner(testProject: testProject, runs: 1, gradleOpts: [heapSize]).run()
-        result.assertEveryBuildSucceeds()
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['eclipse'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | runs | maxExecutionTimeRegression
+        "small"           | 5    | millis(500)
+        "multi"           | 5    | millis(1000)
+        "lotDependencies" | 5    | millis(1000)
+    }
+
+    @Unroll("Project '#testProject' idea")
+    def "idea"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['idea'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+        result.assertCurrentVersionHasNotRegressed()
+
+        where:
+        testProject       | runs | maxExecutionTimeRegression
+        "small"           | 5    | millis(500)
+        "multi"           | 5    | millis(1000)
+        "lotDependencies" | 5    | millis(1000)
+    }
+
+    @Unroll("Project '#testProject'")
+    def "verbose tests with report"() {
+        when:
+        def result = new PerformanceTestRunner(testProject: testProject,
+                tasksToRun: ['cleanTest', 'test'],
+                args: ['-q'],
+                runs: runs,
+                warmUpRuns: 1,
+                maxExecutionTimeRegression: maxExecutionTimeRegression,
+                maxMemoryRegression: kbytes(1400)
+        ).run()
+
+        then:
+        result.assertCurrentVersionHasNotRegressed()
 
         where:
-        testProject | heapSize
-        "small"     | '-Xmx19m' //fails with 16m
-        "multi"     | '-Xmx66m' //fails with 54m on my box, with 60m on ci
+        testProject         | runs | maxExecutionTimeRegression
+        "withTestNG"        | 4    | millis(800)
+        "withVerboseJUnits" | 4    | millis(1200)
     }
 }
\ 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
deleted file mode 100644
index 5f9e104..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
+++ /dev/null
@@ -1,47 +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.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
deleted file mode 100644
index 5404790..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
+++ /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.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
deleted file mode 100644
index 4e109f6..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
+++ /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.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
deleted file mode 100644
index c82f0c7..0000000
--- a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
+++ /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.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.scala b/subprojects/performance/src/templates/Production.scala
new file mode 100644
index 0000000..2553130
--- /dev/null
+++ b/subprojects/performance/src/templates/Production.scala
@@ -0,0 +1,7 @@
+package ${packageName}
+
+class ${productionClassName}(val property: String) {
+<% propertyCount.times { %>
+var prop${it}: String
+<% } %>
+}
diff --git a/subprojects/performance/src/templates/Test.scala b/subprojects/performance/src/templates/Test.scala
new file mode 100644
index 0000000..57398ac
--- /dev/null
+++ b/subprojects/performance/src/templates/Test.scala
@@ -0,0 +1,12 @@
+package ${packageName}
+
+import org.junit.Assert._
+
+class ${testClassName} {
+    val production = new ${productionClassName}("value")
+
+    @org.junit.Test
+    def test() {
+        assertEquals(production.property, "value")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/TestNGTest.java b/subprojects/performance/src/templates/TestNGTest.java
new file mode 100644
index 0000000..bd9d589
--- /dev/null
+++ b/subprojects/performance/src/templates/TestNGTest.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 ${packageName};
+
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class ${testClassName} {
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+    @Test
+    public void testOne() {
+        for (int i = 0; i < 1000; i++) {
+            System.out.println("Some test output from ${testClassName}.testOne - " + i);
+            System.err.println("Some test error  from ${testClassName}.testOne - " + i);
+        }
+        assertEquals(production.getProperty(), "value");
+    }
+
+    @Test
+    public void testTwo() {
+        for (int i = 0; i < 1000; i++) {
+            System.out.println("Some test output from ${testClassName}.testTwo - " + i);
+            System.err.println("Some test error  from ${testClassName}.testTwo - " + i);
+        }
+        assertEquals(production.getProperty(), "value");
+    }
+}
diff --git a/subprojects/performance/src/templates/VerboseJUnitTest.java b/subprojects/performance/src/templates/VerboseJUnitTest.java
new file mode 100644
index 0000000..4cdd535
--- /dev/null
+++ b/subprojects/performance/src/templates/VerboseJUnitTest.java
@@ -0,0 +1,26 @@
+package ${packageName};
+
+import static org.junit.Assert.*;
+
+public class ${testClassName} {
+
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+    @org.junit.Test
+    public void testOne() {
+        for (int i = 0; i < 1000; i++) {
+            System.out.println("Some test output from ${testClassName}.testOne - " + i);
+            System.err.println("Some test error  from ${testClassName}.testOne - " + i);
+        }
+        assertEquals(production.getProperty(), "value");
+    }
+
+    @org.junit.Test
+    public void testTwo() {
+        for (int i = 0; i < 1000; i++) {
+            System.out.println("Some test output from ${testClassName}.testTwo - " + i);
+            System.err.println("Some test error  from ${testClassName}.testTwo - " + i);
+        }
+        assertEquals(production.getProperty(), "value");
+    }
+}
diff --git a/subprojects/performance/src/templates/build.gradle b/subprojects/performance/src/templates/build.gradle
index 9350482..e22fa91 100644
--- a/subprojects/performance/src/templates/build.gradle
+++ b/subprojects/performance/src/templates/build.gradle
@@ -1,21 +1,25 @@
+import java.lang.management.ManagementFactory
+import java.math.RoundingMode
+
 /*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+* Copyright 2010 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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'
+apply plugin: 'idea'
 
 repositories {
 <% if (repository) { %>
@@ -31,9 +35,10 @@ dependencies {
     compile "commons-httpclient:commons-httpclient:3.0"
     compile "commons-codec:commons-codec:1.2"
     compile "org.slf4j:jcl-over-slf4j:1.6.6"
-    compile "org.codehaus.groovy:groovy:1.8.6"
+    compile "org.codehaus.groovy:groovy:1.8.8"
     compile "commons-codec:commons-codec:1.2"
     testCompile 'junit:junit:4.8.2'
+    testCompile 'org.testng:testng:6.4'
     runtime 'com.googlecode:reflectasm:1.01'
 
     <% if (dependencies) { dependencies.each { %>
@@ -43,14 +48,101 @@ dependencies {
 
 test {
     jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
-    testReport = false
+    testReport = <%=testProject.defaults.testReport%>
+    <%=testProject.defaults.testFramework%>
 }
 
 <% if (groovyProject) { %>
 apply plugin: 'groovy'
 dependencies {
-    groovy 'org.codehaus.groovy:groovy:1.8.6'
+    groovy 'org.codehaus.groovy:groovy:1.8.8'
+}
+<% } %>
+
+<% if (scalaProject) { %>
+apply plugin: 'scala'
+dependencies {
+    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+    compile 'org.scala-lang:scala-library:2.9.2'
+}
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.with {
+        useAnt = false
+        fork = true
+        forkOptions.jvmArgs = ["-XX:MaxPermSize=512m"]
+    }
+}
+<% } %>
+
+<% if (withPlainAntCompile) { %>
+class PlainAntCompile extends DefaultTask{
+	File outputDirectoy
+	File sourceDirectory
+	FileCollection classpath;
+
+	@TaskAction
+	void compile(){
+		outputDirectoy.mkdirs()
+		project.ant {
+			property(name:"build.sysclasspath", value:"last")
+			path(id:'classpath') {
+				pathelement(path:classpath.asPath)
+			}
+			path(id:'sourcepath') {
+				pathelement(location:outputDirectoy.absolutePath)
+			}
+			presetdef(name: 'antCompileJava') {
+				javac(  classpathref:'classpath',
+						encoding:'UTF-8',
+						destdir:outputDirectoy.absolutePath,
+						excludes:'',
+						fork:'true',
+						memoryInitialSize:'1024m',
+						memoryMaximumSize:'1536m',
+						source:'1.6',
+						srcdir:sourceDirectory.absolutePath,
+						target:'1.6')
+			}
+		}
+		project.ant.antCompileJava()
+	}
 }
+
+
+task plainAntCompile(type:PlainAntCompile){
+	sourceDirectory = file("src/main/java")
+	outputDirectoy = file("\$buildDir/classes/main")
+	classpath = sourceSets.main.compileClasspath
+}
+
 <% } %>
 
+task dependencyReport(type: DependencyReportTask) {
+    outputFile = new File(buildDir, "dependencies.txt")
+}
 <% } %>
+
+if (project == rootProject) {
+    gradle.buildFinished {
+        def heap = ManagementFactory.memoryMXBean.heapMemoryUsage
+        def nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
+        logger.lifecycle "BEFORE GC"
+        logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
+        logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
+
+        ManagementFactory.memoryMXBean.gc()
+        heap = ManagementFactory.memoryMXBean.heapMemoryUsage
+        nonHeap = ManagementFactory.memoryMXBean.nonHeapMemoryUsage
+        logger.lifecycle "AFTER GC"
+        logger.lifecycle "heap: \${format(heap.used)} (initial \${format(heap.init)}, commited \${format(heap.committed)}, max \${format(heap.max)}"
+        logger.lifecycle "nonHeap: \${format(nonHeap.used)} (initial \${format(nonHeap.init)}, commited \${format(nonHeap.committed)}, max \${format(nonHeap.max)}"
+        buildDir.mkdirs()
+        new File(buildDir, "totalMemoryUsed.txt").text = heap.used
+    }
+}
+
+def format(def value) {
+    value = value / (1024 * 1024)
+    value = value.setScale(4, RoundingMode.DOWN)
+    return "\${value}MB"
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy
new file mode 100644
index 0000000..0ea8747
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/AmountTest.groovy
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Specification
+
+class AmountTest extends Specification {
+
+    def "toString() retains original measurement"() {
+        expect:
+        Amount.valueOf(value, units).toString() == str
+
+        where:
+        value    | units            | str
+        0        | Fruit.apples     | "0 apples"
+        1        | Fruit.apples     | "1 apple"
+        1000     | Fruit.apples     | "1000 apples"
+        0.123    | Fruit.apples     | "0.123 apples"
+        0.333333 | Fruit.apples     | "0.333333 apples"
+        0.5555   | Fruit.apples     | "0.5555 apples"
+        -12      | Fruit.apples     | "-12 apples"
+        145      | Fruit.oranges    | "145 oranges"
+        0.23     | Fruit.grapefruit | "0.23 grapefruit"
+    }
+
+    def "format() displays amount in highest possible units with rounded value"() {
+        expect:
+        Amount.valueOf(value, units).format() == str
+
+        where:
+        value    | units         | str
+        0        | Fruit.apples  | "0 apples"
+        1        | Fruit.apples  | "1 apple"
+        0.032    | Fruit.apples  | "0.032 apples"
+        2.367722 | Fruit.apples  | "2.368 apples"
+        3        | Fruit.apples  | "1 orange"
+        5        | Fruit.apples  | "1 grapefruit"
+        4        | Fruit.apples  | "1.333 oranges"
+        1000     | Fruit.oranges | "600 grapefruit"
+        42       | Fruit.oranges | "25.2 grapefruit"
+        0.45     | Fruit.oranges | "1.35 apples"
+        -12      | Fruit.apples  | "-2.4 grapefruit"
+    }
+
+    def "can convert to specific units"() {
+        expect:
+        Amount.valueOf(value, fromUnits).toUnits(toUnits) == Amount.valueOf(converted, toUnits)
+        Amount.valueOf(value, fromUnits).toUnits(toUnits).toString() == Amount.valueOf(converted, toUnits).toString()
+
+        where:
+        value | fromUnits        | toUnits          | converted
+        21    | Fruit.apples     | Fruit.apples     | 21
+        2.1   | Fruit.oranges    | Fruit.apples     | 6.3
+        1024  | Fruit.grapefruit | Fruit.apples     | 1024 * 5
+        1     | Fruit.apples     | Fruit.grapefruit | 0.2
+        1     | Fruit.apples     | Fruit.oranges    | 0.333333
+    }
+
+    def "amounts are equal when normalised values are the same"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        a.equals(b)
+        b.equals(a)
+        a.hashCode() == b.hashCode()
+        a.compareTo(b) == 0
+        b.compareTo(a) == 0
+
+        where:
+        valueA  | unitsA        | valueB  | unitsB
+        0       | Fruit.apples  | 0       | Fruit.apples
+        0       | Fruit.apples  | 0       | Fruit.oranges
+        9       | Fruit.apples  | 3       | Fruit.oranges
+        5       | Fruit.oranges | 3       | Fruit.grapefruit
+        6.3399  | Fruit.apples  | 2.1133  | Fruit.oranges
+        0.333   | Fruit.apples  | 0.333   | Fruit.apples
+        0.55667 | Fruit.apples  | 0.55667 | Fruit.apples
+    }
+
+    def "amounts are not equal when normalised values are different"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        !a.equals(b)
+        !b.equals(a)
+        a.hashCode() != b.hashCode()
+        a.compareTo(b) != 0
+        b.compareTo(a) != 0
+
+        where:
+        valueA | unitsA           | valueB  | unitsB
+        0      | Fruit.apples     | 1       | Fruit.apples
+        0.334  | Fruit.apples     | 0.333   | Fruit.apples
+        0.334  | Fruit.apples     | 0.33433 | Fruit.apples
+        1      | Fruit.apples     | 1       | Fruit.oranges
+        1      | Fruit.grapefruit | 1       | Fruit.oranges
+    }
+
+    def "can compare values"() {
+        expect:
+        def a = Amount.valueOf(valueA, unitsA)
+        def b = Amount.valueOf(valueB, unitsB)
+        a < b
+        b > a
+        a != b
+        b != a
+
+        where:
+        valueA  | unitsA        | valueB | unitsB
+        0.1     | Fruit.oranges | 0.101  | Fruit.oranges
+        0.33333 | Fruit.oranges | 1      | Fruit.apples
+        1       | Fruit.apples  | 1      | Fruit.oranges
+        1       | Fruit.oranges | 1      | Fruit.grapefruit
+        -1      | Fruit.apples  | 0      | Fruit.oranges
+        5.9     | Fruit.apples  | 2      | Fruit.oranges
+    }
+
+    def "can add amounts with same units"() {
+        expect:
+        Amount.valueOf(a, units) + Amount.valueOf(b, units) == Amount.valueOf(c, units)
+
+        where:
+        a    | b     | c      | units
+        0    | 0     | 0      | Fruit.apples
+        1    | 2     | 3      | Fruit.apples
+        1    | 2     | 3      | Fruit.oranges
+        23.4 | 0.567 | 23.967 | Fruit.apples
+    }
+
+    def "can add amounts with different units of same quantity"() {
+        def sum = Amount.valueOf(valueA, unitsA) + Amount.valueOf(valueB, unitsB)
+        expect:
+        sum == Amount.valueOf(valueC, unitsC)
+        sum.toString() == Amount.valueOf(valueC, unitsC).toString()
+
+        where:
+        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
+        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
+        0      | Fruit.apples  | 12     | Fruit.oranges | 12      | Fruit.oranges
+        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
+        1      | Fruit.apples  | 2      | Fruit.oranges | 7       | Fruit.apples
+        3      | Fruit.oranges | 4      | Fruit.oranges | 7       | Fruit.oranges
+        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 13.1166 | Fruit.apples
+    }
+
+    def "can subtract amounts with same units"() {
+        expect:
+        Amount.valueOf(a, units) - Amount.valueOf(b, units) == Amount.valueOf(c, units)
+
+        where:
+        a    | b     | c      | units
+        0    | 0     | 0      | Fruit.apples
+        2    | 1     | 1      | Fruit.apples
+        123  | 12    | 111    | Fruit.oranges
+        10   | 56    | -46    | Fruit.apples
+        23.4 | 0.567 | 22.833 | Fruit.apples
+    }
+
+    def "can subtract amounts with different units of same quantity"() {
+        def difference = Amount.valueOf(valueA, unitsA) - Amount.valueOf(valueB, unitsB)
+        expect:
+        difference == Amount.valueOf(valueC, unitsC)
+        difference.toString() == Amount.valueOf(valueC, unitsC).toString()
+
+        where:
+        valueA | unitsA        | valueB | unitsB        | valueC  | unitsC
+        0      | Fruit.apples  | 0      | Fruit.oranges | 0       | Fruit.apples
+        1      | Fruit.oranges | 0      | Fruit.apples  | 1       | Fruit.oranges
+        4      | Fruit.apples  | 1      | Fruit.oranges | 1       | Fruit.apples
+        2      | Fruit.apples  | 1      | Fruit.oranges | -1      | Fruit.apples
+        0      | Fruit.apples  | 12     | Fruit.oranges | -36     | Fruit.apples
+        0.7    | Fruit.apples  | 0      | Fruit.oranges | 0.7     | Fruit.apples
+        0.7    | Fruit.oranges | 1      | Fruit.apples  | 1.1     | Fruit.apples
+        12.45  | Fruit.apples  | 0.2222 | Fruit.oranges | 11.7834 | Fruit.apples
+    }
+
+    def "can divide amount by a unitless value"() {
+        expect:
+        Amount.valueOf(a, Fruit.apples) / b == Amount.valueOf(c, Fruit.apples)
+
+        where:
+        a  | b   | c
+        0  | 200 | 0
+        2  | 1   | 2
+        5  | 10  | 0.5
+        10 | -2  | -5
+        1  | 3   | 0.333333
+        2  | 3   | 0.666667
+    }
+
+    def "can divide amount by another amount of same quantity"() {
+        expect:
+        Amount.valueOf(valueA, unitsA) / Amount.valueOf(valueB, unitsB) == result
+
+        where:
+        valueA | unitsA        | valueB | unitsB           | result
+        0      | Fruit.apples  | 200    | Fruit.apples     | 0
+        2      | Fruit.apples  | 1      | Fruit.apples     | 2
+        5      | Fruit.apples  | 10     | Fruit.apples     | 0.5
+        10     | Fruit.apples  | -2     | Fruit.apples     | -5
+        1      | Fruit.apples  | 3      | Fruit.apples     | 0.333333
+        2      | Fruit.apples  | 3      | Fruit.apples     | 0.666667
+        4      | Fruit.apples  | 1.2    | Fruit.oranges    | 1.111111
+        125    | Fruit.oranges | 23.4   | Fruit.grapefruit | 3.205128
+    }
+
+    def "can convert to absolute value"() {
+        expect:
+        Amount.valueOf(12, Fruit.grapefruit).abs() == Amount.valueOf(12, Fruit.grapefruit)
+        Amount.valueOf(-34, Fruit.grapefruit).abs() == Amount.valueOf(34, Fruit.grapefruit)
+    }
+
+    public static class Fruit {
+        static def apples = Units.base(Fruit.class, "apple", "apples")
+        static def oranges = apples.times(3, "orange", "oranges")
+        static def grapefruit = apples.times(5, "grapefruit")
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.groovy
new file mode 100644
index 0000000..f98d309
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/DurationTest.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.peformance.fixture
+
+import spock.lang.Specification
+
+class DurationTest extends Specification {
+    def "millisecond durations have useful string formats"() {
+        expect:
+        Duration.millis(millis).toString() == str
+        Duration.millis(millis).format() == formatted
+
+        where:
+        millis  | str          | formatted
+        0       | "0 ms"       | "0 ms"
+        1       | "1 ms"       | "1 ms"
+        0.123   | "0.123 ms"   | "0.123 ms"
+        -12     | "-12 ms"     | "-12 ms"
+        1000    | "1000 ms"    | "1 s"
+        123456  | "123456 ms"  | "2.058 m"
+        3607000 | "3607000 ms" | "1.002 h"
+    }
+
+    def "second durations have useful string formats"() {
+        expect:
+        Duration.seconds(millis).toString() == str
+
+        where:
+        millis    | str           | format
+        0         | "0 s"         | "0 ms"
+        1         | "1 s"         | "1 s"
+        1000      | "1000 s"      | "16.667 m"
+        0.123     | "0.123 s"     | "123 ms"
+        0.1234567 | "0.1234567 s" | "123.457 ms"
+        -12       | "-12 s"       | "-12 s"
+    }
+
+    def "can convert between units"() {
+        expect:
+        Duration.millis(45000) == Duration.seconds(45)
+        Duration.seconds(0.98) == Duration.millis(980)
+        Duration.seconds(120) == Duration.minutes(2)
+        Duration.seconds(30) == Duration.minutes(0.5)
+        Duration.hours(30) == Duration.millis(30 * 60 * 60 * 1000)
+    }
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy
new file mode 100644
index 0000000..3f62364
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PerformanceResultsTest.groovy
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 spock.lang.Specification
+
+class PerformanceResultsTest extends Specification {
+    def PerformanceResults result = new PerformanceResults()
+
+    def "passes when average execution time for current release is smaller than average execution time for previous release"() {
+        given:
+        result.previous.add(operation(executionTime: 110))
+        result.previous.add(operation(executionTime: 100))
+        result.previous.add(operation(executionTime: 90))
+
+        and:
+        result.current.add(operation(executionTime: 90))
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 90))
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "passes when average execution time for current release is within specified range of average execution time for previous release"() {
+        given:
+        result.maxExecutionTimeRegression = Duration.millis(10)
+        result.previous.add(operation(executionTime: 100))
+        result.previous.add(operation(executionTime: 100))
+        result.previous.add(operation(executionTime: 100))
+
+        and:
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 110))
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "fails when average execution time for current release is larger than average execution time for previous release"() {
+        given:
+        result.displayName = '<test>'
+        result.maxExecutionTimeRegression = Duration.millis(10)
+        result.previous.add(operation(executionTime: 100))
+        result.previous.add(operation(executionTime: 100))
+        result.previous.add(operation(executionTime: 100))
+
+        and:
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 110))
+        result.current.add(operation(executionTime: 111))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith('Speed <test>: current Gradle is a little slower on average.')
+        e.message.contains('Difference: 10.333 ms slower (10.333 ms), 10.33%')
+    }
+
+    def "passes when average heap usage for current release is smaller than average heap usage for previous release"() {
+        given:
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+
+        and:
+        result.current.add(operation(heapUsed: 1000))
+        result.current.add(operation(heapUsed: 1005))
+        result.current.add(operation(heapUsed: 994))
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "passes when average heap usage for current release is slightly larger than average heap usage for previous release"() {
+        given:
+        result.maxMemoryRegression = DataAmount.bytes(100)
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+
+        and:
+        result.current.add(operation(heapUsed: 1100))
+        result.current.add(operation(heapUsed: 1100))
+        result.current.add(operation(heapUsed: 1100))
+
+        expect:
+        result.assertCurrentVersionHasNotRegressed()
+    }
+
+    def "fails when average heap usage for current release is larger than average heap usage for previous release"() {
+        given:
+        result.displayName = '<test>'
+        result.maxMemoryRegression = DataAmount.bytes(100)
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+        result.previous.add(operation(heapUsed: 1000))
+
+        and:
+        result.current.add(operation(heapUsed: 1100))
+        result.current.add(operation(heapUsed: 1100))
+        result.current.add(operation(heapUsed: 1101))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith('Memory <test>: current Gradle needs a little more memory on average.')
+        e.message.contains('Difference: 100.333 B more (100.333 B), 10.03%')
+    }
+
+    def "fails when both heap usage and execution time have regressed"() {
+        given:
+        result.displayName = '<test>'
+        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
+        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
+        result.previous.add(operation(heapUsed: 1000, executionTime: 100))
+
+        and:
+        result.current.add(operation(heapUsed: 1100, executionTime: 110))
+        result.current.add(operation(heapUsed: 1100, executionTime: 110))
+        result.current.add(operation(heapUsed: 1101, executionTime: 111))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.contains('Speed <test>: current Gradle is a little slower on average.')
+        e.message.contains('Difference: 10.333 ms slower (10.333 ms)')
+        e.message.contains('Memory <test>: current Gradle needs a little more memory on average.')
+        e.message.contains('Difference: 100.333 B more (100.333 B)')
+    }
+
+    def "fails when a previous operation fails"() {
+        given:
+        result.previous.add(operation(failure: new RuntimeException()))
+        result.current.add(operation())
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    def "fails when a current operation fails"() {
+        given:
+        result.previous.add(operation())
+        result.current.add(operation(failure: new RuntimeException()))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    def "fails when an operation fails"() {
+        given:
+        result.others.oldVersion = new MeasuredOperationList()
+        result.previous.add(operation())
+        result.current.add(operation())
+        result.others.oldVersion.add(operation(failure: new RuntimeException()))
+
+        when:
+        result.assertCurrentVersionHasNotRegressed()
+
+        then:
+        AssertionError e = thrown()
+        e.message.startsWith("Some builds have failed.")
+    }
+
+    private MeasuredOperation operation(Map<String, Object> args) {
+        def operation = new MeasuredOperation()
+        operation.executionTime = Duration.millis(args?.executionTime ?: 120)
+        operation.totalMemoryUsed = DataAmount.bytes(args?.heapUsed ?: 1024)
+        operation.exception = args?.failure
+        return operation
+    }
+
+}
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.groovy
new file mode 100644
index 0000000..4435b0c
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/PrettyCalculatorSpec.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.peformance.fixture
+
+import spock.lang.Specification
+
+import static org.gradle.peformance.fixture.PrettyCalculator.percentChange
+
+/**
+ * by Szczepan Faber, created at: 10/30/12
+ */
+class PrettyCalculatorSpec extends Specification {
+
+    def "knows percentage change"() {
+        expect:
+        percentChange(Amount.valueOf(current, Duration.SECONDS), Amount.valueOf(prevous, Duration.SECONDS)) == percent
+
+        where:
+        current  | prevous | percent
+        1        | 1       | 0
+        3        | 1       | 200
+        1        | 3       | -66.67
+        2        | 3       | -33.33
+        5        | 4       | 25
+        2.2      | 3.567   | -38.32
+        12.00001 | 10.23   | 17.30
+        //not strictly true, but for our purposes should do:
+        0        | 3       | -100
+        300      | 0       | 100
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.groovy b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.groovy
new file mode 100644
index 0000000..98c8509
--- /dev/null
+++ b/subprojects/performance/src/test/groovy/org/gradle/peformance/fixture/UnitsTest.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.peformance.fixture
+
+import spock.lang.Specification
+
+class UnitsTest extends Specification {
+    def "can compare units of same quantity"() {
+        def base = Units.base(Void.class, "base")
+        def units1 = base.times(12, "units1")
+        def units2 = base.times(33, "units2")
+
+        expect:
+        base < units1
+        base < units2
+        units1 > base
+        units1 < units2
+        units2 > base
+        units2 > units1
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.java
new file mode 100644
index 0000000..8b348a2
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Amount.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.peformance.fixture;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.List;
+
+/**
+ * An amount is an immutable value of some quantity, such as duration or length. Each amount has a decimal value and associated units.
+ *
+ * TODO - need to sort out scaling when dividing or converting between units.
+ */
+public class Amount<Q> implements Comparable<Amount<Q>> {
+    private final BigDecimal value;
+    private final Units<Q> units;
+    private final BigDecimal normalised;
+
+    private Amount(BigDecimal value, Units<Q> units) {
+        this.value = value;
+        this.units = units;
+        normalised = units.scaleTo(value, units.getBaseUnits());
+    }
+
+    public static <Q> Amount<Q> valueOf(long value, Units<Q> units) {
+        return valueOf(BigDecimal.valueOf(value), units);
+    }
+
+    public static <Q> Amount<Q> valueOf(BigDecimal value, Units<Q> units) {
+        return new Amount<Q>(value, units);
+    }
+
+    /**
+     * Returns a string representation of this amount. Uses the original value and units of this amount.
+     */
+    @Override
+    public String toString() {
+        return String.format("%s %s", value, units.format(value));
+    }
+
+    public Units<Q> getUnits() {
+        return units;
+    }
+
+    public BigDecimal getValue() {
+        return value;
+    }
+
+    /**
+     * Returns a human consumable string representation of this amount.
+     */
+    public String format() {
+        List<Units<Q>> allUnits = units.getUnitsForQuantity();
+        BigDecimal base = normalised.abs();
+        for (int i = allUnits.size()-1; i >= 0; i--) {
+            Units<Q> candidate = allUnits.get(i);
+            if (base.compareTo(candidate.getFactor()) >= 0) {
+                BigDecimal scaled = units.scaleTo(value, candidate);
+                return String.format("%s %s", new DecimalFormat("#.###").format(scaled), candidate.format(scaled));
+            }
+        }
+        return String.format("%s %s", new DecimalFormat("#.###").format(value), units.format(value));
+    }
+
+    /**
+     * Converts this amount to an equivalent amount in the specified units.
+     *
+     * @return The converted amount.
+     */
+    public Amount<Q> toUnits(Units<Q> units) {
+        if (units.equals(this.units)) {
+            return this;
+        }
+        return new Amount<Q>(this.units.scaleTo(value, units), units);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        Amount<Q> other = (Amount<Q>) obj;
+        return compareTo(other) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return normalised.hashCode();
+    }
+
+    public int compareTo(Amount<Q> o) {
+        if (o.units.getType() != units.getType()) {
+            throw new IllegalArgumentException(String.format("Cannot compare amount with units %s.", o.units));
+        }
+        return normalised.compareTo(o.normalised);
+    }
+
+    public Amount<Q> plus(Amount<Q> other) {
+        if (other.value.equals(BigDecimal.ZERO)) {
+            return this;
+        }
+        if (value.equals(BigDecimal.ZERO)) {
+            return other;
+        }
+        int diff = units.compareTo(other.units);
+        if (diff == 0) {
+            return new Amount<Q>(value.add(other.value), units);
+        }
+        if (diff < 0) {
+            return new Amount<Q>(value.add(other.units.scaleTo(other.value, units)), units);
+        }
+        return new Amount<Q>(units.scaleTo(value, other.units).add(other.value), other.units);
+    }
+
+    public Amount<Q> minus(Amount<Q> other) {
+        if (other.value.equals(BigDecimal.ZERO)) {
+            return this;
+        }
+        int diff = units.compareTo(other.units);
+        if (diff == 0) {
+            return new Amount<Q>(value.subtract(other.value), units);
+        }
+        if (diff < 0) {
+            return new Amount<Q>(value.subtract(other.units.scaleTo(other.value, units)), units);
+        }
+        return new Amount<Q>(units.scaleTo(value, other.units).subtract(other.value), other.units);
+    }
+
+    public Amount<Q> div(long other) {
+        return new Amount<Q>(value.divide(BigDecimal.valueOf(other), 6, RoundingMode.HALF_UP), units);
+    }
+
+    public BigDecimal div(Amount<Q> other) {
+        return normalised.divide(other.normalised, 6, RoundingMode.HALF_UP);
+    }
+
+    public Amount<Q> abs() {
+        if (value.compareTo(BigDecimal.ZERO) >= 0) {
+            return this;
+        }
+        return new Amount<Q>(value.abs(), units);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.java
new file mode 100644
index 0000000..574e8d9
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataAmount.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.peformance.fixture;
+
+import java.math.BigDecimal;
+
+public class DataAmount {
+    public static final Units<DataAmount> BYTES = Units.base(DataAmount.class, "B");
+    public static final Units<DataAmount> KILO_BYTES = BYTES.times(1024, "kB");
+    public static final Units<DataAmount> MEGA_BYTES = KILO_BYTES.times(1024, "MB");
+    public static final Units<DataAmount> GIGA_BYTES = MEGA_BYTES.times(1024, "GB");
+
+    public static Amount<DataAmount> bytes(long value) {
+        return Amount.valueOf(value, BYTES);
+    }
+
+    public static Amount<DataAmount> bytes(BigDecimal value) {
+        return Amount.valueOf(value, BYTES);
+    }
+
+    public static Amount<DataAmount> kbytes(BigDecimal value) {
+        return Amount.valueOf(value, KILO_BYTES);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.java
new file mode 100644
index 0000000..7eeee84
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/DataCollector.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.peformance.fixture;
+
+import java.io.File;
+
+/**
+ * by Szczepan Faber, created at: 8/14/12
+ */
+public interface DataCollector {
+
+    void collect(File testProjectDir, MeasuredOperation operation);
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.java
new file mode 100644
index 0000000..2422d94
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Duration.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.peformance.fixture;
+
+import java.math.BigDecimal;
+
+public class Duration {
+    public static final Units<Duration> MILLI_SECONDS = Units.base(Duration.class, "ms");
+    public static final Units<Duration> SECONDS = MILLI_SECONDS.times(1000, "s");
+    public static final Units<Duration> MINUTES = SECONDS.times(60, "m");
+    public static final Units<Duration> HOURS = MINUTES.times(60, "h");
+
+    public static Amount<Duration> millis(long millis) {
+        return Amount.valueOf(millis, MILLI_SECONDS);
+    }
+
+    public static Amount<Duration> millis(BigDecimal millis) {
+        return Amount.valueOf(millis, MILLI_SECONDS);
+    }
+
+    public static Amount<Duration> seconds(BigDecimal seconds) {
+        return Amount.valueOf(seconds, SECONDS);
+    }
+
+    public static Amount<Duration> minutes(BigDecimal minutes) {
+        return Amount.valueOf(minutes, MINUTES);
+    }
+
+    public static Amount<Duration> hours(BigDecimal hours) {
+        return Amount.valueOf(hours, HOURS);
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
new file mode 100644
index 0000000..6c1c32e
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
@@ -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.peformance.fixture
+
+import org.gradle.util.Clock
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+public class MeasuredOperation {
+    Amount<Duration> executionTime
+    Exception exception
+    Amount<DataAmount> totalMemoryUsed
+
+    static MeasuredOperation measure(Closure operation) {
+        def out = new MeasuredOperation()
+        def clock = new Clock()
+        clock.reset()
+        try {
+            operation(out)
+        } catch (Exception e) {
+            out.exception = e
+        }
+        out.executionTime = Duration.millis(clock.timeInMs)
+        return out
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.groovy
new file mode 100644
index 0000000..04f3c52
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MeasuredOperationList.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.peformance.fixture
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+public class MeasuredOperationList extends LinkedList<MeasuredOperation> {
+    String name
+
+    Amount<DataAmount> avgMemory() {
+        def bytes = this.collect { it.totalMemoryUsed }
+        bytes.sum() / bytes.size()
+    }
+
+    Amount<DataAmount> minMemory() {
+        return collect { it.totalMemoryUsed }.min()
+    }
+
+    Amount<DataAmount> maxMemory() {
+        return collect { it.totalMemoryUsed }.max()
+    }
+
+    Amount<Duration> avgTime() {
+        def currentTimes = this.collect { it.executionTime }
+        currentTimes.sum() / currentTimes.size()
+    }
+
+    Amount<Duration> maxTime() {
+        return collect { it.executionTime }.max()
+    }
+
+    Amount<Duration> minTime() {
+        return collect { it.executionTime }.min()
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.groovy
new file mode 100644
index 0000000..475292c
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/MemoryInfoCollector.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.peformance.fixture
+
+/**
+ * by Szczepan Faber, created at: 8/14/12
+ */
+public class MemoryInfoCollector implements DataCollector {
+
+    String outputFileName
+
+    public void collect(File testProjectDir, MeasuredOperation operation) {
+        def file = new File(testProjectDir, outputFileName)
+        if (!file.exists()) {
+            throw new RuntimeException("Cannot find $file. Cannot collect memory information if the build hasn't produced one.")
+        }
+        long bytesConsumed = Long.parseLong(file.text)
+        operation.totalMemoryUsed = DataAmount.bytes(bytesConsumed)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
new file mode 100644
index 0000000..872f078
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static PrettyCalculator.prettyBytes
+import static PrettyCalculator.prettyTime
+import static org.gradle.peformance.fixture.PrettyCalculator.toBytes
+import static org.gradle.peformance.fixture.PrettyCalculator.toMillis
+
+public class PerformanceResults {
+
+    String displayName
+    Amount<Duration> maxExecutionTimeRegression = Duration.millis(0)
+    Amount<DataAmount> maxMemoryRegression = DataAmount.bytes(0)
+
+    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
+
+    final MeasuredOperationList previous = new MeasuredOperationList(name: "Previous release")
+    final MeasuredOperationList current = new MeasuredOperationList(name:  "Current Gradle")
+    final Map<String, MeasuredOperationList> others = new TreeMap<>()
+
+    def clear() {
+        previous.clear();
+        current.clear();
+        others.values()*.clear()
+    }
+
+    void assertEveryBuildSucceeds() {
+        LOGGER.info("Asserting all builds have succeeded...");
+        assert previous.size() == current.size()
+        def failures = []
+        failures.addAll previous.findAll { it.exception }
+        others.values().each {
+            failures.addAll it.findAll { it.exception }
+        }
+        failures.addAll current.findAll { it.exception }
+        assert failures.collect { it.exception }.empty : "Some builds have failed."
+    }
+
+    void assertCurrentVersionHasNotRegressed() {
+        def slower = assertCurrentReleaseIsNotSlower()
+        def larger = assertMemoryUsed()
+        if (slower && larger) {
+            throw new AssertionError("$slower\n$larger")
+        }
+        if (slower) {
+            throw new AssertionError(slower)
+        }
+        if (larger) {
+            throw new AssertionError(larger)
+        }
+        assertEveryBuildSucceeds()
+    }
+
+    private String assertMemoryUsed() {
+        def failed = (current.avgMemory() - previous.avgMemory()) > maxMemoryRegression
+
+        String message;
+        if (current.avgMemory() > previous.avgMemory()) {
+            message = "Memory $displayName: current Gradle needs a little more memory on average."
+        } else {
+            message = "Memory $displayName: AWESOME! current Gradle needs less memory on average :D"
+        }
+        message += "\n${memoryStats()}"
+        println("\n$message")
+        return failed ? message : null
+    }
+
+    private String assertCurrentReleaseIsNotSlower() {
+        def failed = (current.avgTime() - previous.avgTime()) > maxExecutionTimeRegression
+
+        String message;
+        if (current.avgTime() > previous.avgTime()) {
+            message = "Speed $displayName: current Gradle is a little slower on average."
+        } else {
+            message = "Speed $displayName: AWESOME! current Gradle is faster on average :D"
+        }
+        message += "\n${speedStats()}"
+        println("\n$message")
+        return failed ? message : null
+    }
+
+    String memoryStats() {
+        def result = new StringBuilder()
+        result.append(memoryStats(previous))
+        others.values().each {
+            result.append(memoryStats(it))
+        }
+        result.append(memoryStats(current))
+        def diff = current.avgMemory() - previous.avgMemory()
+        def desc = diff > DataAmount.bytes(0) ? "more" : "less"
+        result.append("Difference: ${prettyBytes(diff.abs())} $desc (${toBytes(diff.abs())}), ${PrettyCalculator.percentChange(current.avgMemory(), previous.avgMemory())}%, max regression: ${prettyBytes(maxMemoryRegression)}")
+        return result.toString()
+    }
+
+    String memoryStats(MeasuredOperationList list) {
+        """  ${list.name} avg: ${prettyBytes(list.avgMemory())} ${list.collect { prettyBytes(it.totalMemoryUsed) }}
+  ${list.name} min: ${prettyBytes(list.minMemory())}, max: ${prettyBytes(list.maxMemory())}
+"""
+    }
+
+    String speedStats() {
+        def result = new StringBuilder()
+        result.append(speedStats(previous))
+        others.values().each {
+            result.append(speedStats(it))
+        }
+        result.append(speedStats(current))
+        def diff = current.avgTime() - previous.avgTime()
+        def desc = diff > Duration.millis(0) ? "slower" : "faster"
+        result.append("Difference: ${prettyTime(diff.abs())} $desc (${toMillis(diff.abs())}), ${PrettyCalculator.percentChange(current.avgTime(), previous.avgTime())}%, max regression: ${prettyTime(maxExecutionTimeRegression)}")
+        return result.toString()
+    }
+
+    String speedStats(MeasuredOperationList list) {
+        """  ${list.name} avg: ${prettyTime(list.avgTime())} ${list.collect { prettyTime(it.executionTime) }}
+  ${list.name} min: ${prettyTime(list.minTime())}, max: ${prettyTime(list.maxTime())}
+"""
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
new file mode 100644
index 0000000..7dc458e
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
+    Amount<Duration> maxExecutionTimeRegression
+    Amount<DataAmount> maxMemoryRegression
+    List<String> otherVersions = []
+    List<String> tasksToRun = []
+    DataCollector dataCollector = new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt")
+    List<String> args = []
+
+    PerformanceResults results
+
+    PerformanceResults run() {
+        results = new PerformanceResults(maxExecutionTimeRegression: maxExecutionTimeRegression, maxMemoryRegression: maxMemoryRegression, displayName: "Results for test project '$testProject' with tasks ${tasksToRun.join(', ')}")
+        results.previous.name = "Gradle ${previous.version}"
+        otherVersions.each { results.others[it] = new MeasuredOperationList(name: "Gradle $it") }
+
+        println "Running performance tests for test project '$testProject', no. of runs: $runs"
+        warmUpRuns.times {
+            println "Executing warm-up run #${it + 1}"
+            runOnce()
+        }
+        results.clear()
+        runs.times {
+            println "Executing test run #${it + 1}"
+            runOnce()
+        }
+        results
+    }
+
+    void runOnce() {
+        File projectDir = new TestProjectLocator().findProjectDir(testProject)
+        runOnce(previous, projectDir, results.previous)
+        otherVersions.each {
+            runOnce(current.previousVersion(it), projectDir, results.others[it])
+        }
+        runOnce(current, projectDir, results.current)
+    }
+
+    void runOnce(BasicGradleDistribution dist, File projectDir, MeasuredOperationList results) {
+        def executer = this.executer(dist, projectDir)
+        def operation = MeasuredOperation.measure { MeasuredOperation operation ->
+            executer.run()
+        }
+        dataCollector.collect(projectDir, operation)
+        results.add(operation)
+    }
+
+    GradleExecuter executer(BasicGradleDistribution dist, File projectDir) {
+        def executer
+        if (dist instanceof GradleDistribution) {
+            executer = new GradleDistributionExecuter(GradleDistributionExecuter.Executer.forking, dist)
+            executer.withDeprecationChecksDisabled()
+            executer.withStackTraceChecksDisabled()
+        } else {
+            executer = dist.executer()
+        }
+        executer.withGradleUserHomeDir(current.userHomeDir)
+        return executer.withArguments('-u').inDirectory(projectDir).withTasks(tasksToRun).withArguments(args)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.groovy
new file mode 100644
index 0000000..5538326
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/PrettyCalculator.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.peformance.fixture
+
+import java.math.RoundingMode
+
+/**
+ * by Szczepan Faber, created at: 10/30/12
+ */
+class PrettyCalculator {
+
+    static String prettyBytes(Amount<DataAmount> bytes) {
+        return bytes.format()
+    }
+
+    static String toBytes(Amount<DataAmount> bytes) {
+        return bytes.toUnits(DataAmount.BYTES).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " B"
+    }
+
+    static String prettyTime(Amount<Duration> duration) {
+        return duration.format()
+    }
+
+    static String toMillis(Amount<Duration> duration) {
+        return duration.toUnits(Duration.MILLI_SECONDS).value.setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toString() + " ms"
+    }
+
+    static <Q> Number percentChange(Amount<Q> current, Amount<Q> previous) {
+        if (previous == Amount.valueOf(0, previous.getUnits())) {
+            return 100
+        }
+        BigDecimal result = (current - previous) / previous * 100
+        return result.setScale(2, RoundingMode.HALF_UP)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
new file mode 100644
index 0000000..0f3d530
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/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."
+        throw new IllegalArgumentException(message)
+    }
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java
new file mode 100644
index 0000000..1fe5f8a
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/peformance/fixture/Units.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class Units<Q> implements Comparable<Units<Q>> {
+    private final Class<Q> type;
+    private final String displaySingular;
+    private final String displayPlural;
+
+    protected Units(Class<Q> type, String displaySingular, String displayPlural) {
+        this.type = type;
+        this.displaySingular = displaySingular;
+        this.displayPlural = displayPlural;
+    }
+
+    /**
+     * Creates the base units for a given quantity.
+     */
+    public static <Q> Units<Q> base(Class<Q> type, String displaySingular) {
+        return new BaseUnits<Q>(type, displaySingular, displaySingular);
+    }
+
+    /**
+     * Creates the base units for a given quantity.
+     */
+    public static <Q> Units<Q> base(Class<Q> type, String displaySingular, String displayPlural) {
+        return new BaseUnits<Q>(type, displaySingular, displayPlural);
+    }
+
+    protected Class<Q> getType() {
+        return type;
+    }
+
+    @Override
+    public String toString() {
+        return displayPlural;
+    }
+
+    /**
+     * Creates units that are some multiple of this units.
+     */
+    public abstract Units<Q> times(long value, String displaySingular, String displayPlural);
+
+    /**
+     * Creates units that are some multiple of this units.
+     */
+    public Units<Q> times(long value, String displaySingular) {
+        return times(value, displaySingular, displaySingular);
+    }
+
+    protected abstract Units<Q> getBaseUnits();
+
+    protected abstract List<Units<Q>> getUnitsForQuantity();
+
+    protected abstract BigDecimal getFactor();
+
+    protected abstract BigDecimal scaleTo(BigDecimal value, Units<Q> units);
+
+    protected String format(BigDecimal value) {
+        return value.compareTo(BigDecimal.ONE) == 0 ? displaySingular : displayPlural;
+    }
+
+    private static class BaseUnits<Q> extends Units<Q> {
+        private final List<Units<Q>> units = new ArrayList<Units<Q>>();
+
+        protected BaseUnits(Class<Q> type, String displaySingular, String displayPlural) {
+            super(type, displaySingular, displayPlural);
+            units.add(this);
+        }
+
+        @Override
+        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
+            if (units == this) {
+                return value;
+            }
+            ScaledUnits<Q> scaledUnits = (ScaledUnits<Q>) units;
+            return value.divide(scaledUnits.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
+        }
+
+        @Override
+        protected List<Units<Q>> getUnitsForQuantity() {
+            return units;
+        }
+
+        @Override
+        public Units<Q> times(long value, String displaySingular, String displayPlural) {
+            return new ScaledUnits<Q>(this, displaySingular, displayPlural, BigDecimal.valueOf(value));
+        }
+
+        @Override
+        protected Units<Q> getBaseUnits() {
+            return this;
+        }
+
+        @Override
+        protected BigDecimal getFactor() {
+            return BigDecimal.ONE;
+        }
+
+        public int compareTo(Units<Q> o) {
+            if (o == this) {
+                return 0;
+            }
+            if (o.getType() != getType()) {
+                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
+            }
+            return -1;
+        }
+
+        public void add(ScaledUnits<Q> units) {
+            this.units.add(units);
+            Collections.sort(this.units);
+        }
+    }
+
+    private static class ScaledUnits<Q> extends Units<Q> {
+        private final BaseUnits<Q> baseUnits;
+        private final BigDecimal factor;
+
+        public ScaledUnits(BaseUnits<Q> baseUnits, String displaySingular, String displayPlural, BigDecimal factor) {
+            super(baseUnits.getType(), displaySingular, displayPlural);
+            assert factor.compareTo(BigDecimal.ONE) > 0;
+            this.baseUnits = baseUnits;
+            this.factor = factor;
+            baseUnits.add(this);
+        }
+
+        @Override
+        public Units<Q> times(long value, String displaySingular, String displayPlural) {
+            return new ScaledUnits<Q>(baseUnits, displaySingular, displayPlural, factor.multiply(BigDecimal.valueOf(value)));
+        }
+
+        @Override
+        public BigDecimal scaleTo(BigDecimal value, Units<Q> units) {
+            if (units == this) {
+                return value;
+            }
+            if (units.equals(baseUnits)) {
+                return value.multiply(factor);
+            }
+            ScaledUnits<Q> other = (ScaledUnits<Q>) units;
+            return value.multiply(factor).divide(other.factor, 6, RoundingMode.HALF_UP).stripTrailingZeros();
+        }
+
+        @Override
+        protected List<Units<Q>> getUnitsForQuantity() {
+            return baseUnits.getUnitsForQuantity();
+        }
+
+        @Override
+        protected Units<Q> getBaseUnits() {
+            return baseUnits;
+        }
+
+        @Override
+        protected BigDecimal getFactor() {
+            return factor;
+        }
+
+        public int compareTo(Units<Q> o) {
+            if (o.getType() != getType()) {
+                throw new IllegalArgumentException(String.format("Cannot compare units of %s with units of %s.", getType(), o.getType()));
+            }
+            if (o.equals(baseUnits)) {
+                return 1;
+            }
+            ScaledUnits<Q> other = (ScaledUnits<Q>) o;
+            if (!other.baseUnits.equals(baseUnits)) {
+                throw new IllegalArgumentException("Cannot compare units with different base units.");
+            }
+            return factor.compareTo(other.factor);
+        }
+    }
+}
diff --git a/subprojects/plugins/plugins.gradle b/subprojects/plugins/plugins.gradle
index 5a4ee68..b163af0 100644
--- a/subprojects/plugins/plugins.gradle
+++ b/subprojects/plugins/plugins.gradle
@@ -20,7 +20,7 @@ configurations {
     testFixtures
 }
 
-if (!Jvm.current().java6Compatible) {
+if (!javaVersion.java6Compatible) {
     sourceSets.main.groovy.exclude '**/jdk6/**'
 }
 
@@ -28,7 +28,9 @@ dependencies {
     groovy libraries.groovy
 
     compile project(':core')
+    compile project(':coreImpl')
     compile project(':wrapper')
+    compile project(':reporting')
 
     compile libraries.ant
     compile libraries.asm
@@ -38,7 +40,7 @@ dependencies {
     compile libraries.slf4j_api
     compile 'org.testng:testng:6.3.1'
 
-    provided files(Jvm.current().toolsJar) // for SunJavaCompiler
+    provided files(jvm.toolsJar) // for SunJavaCompiler
 
     runtime libraries.commons_cli
 
@@ -55,8 +57,8 @@ 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
+useTestFixtures(sourceSet: "testFixtures")
+useTestFixtures(project: ":coreImpl")
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
index 893f01a..6f497d4 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
@@ -24,7 +24,7 @@ class BasePluginIntegrationTest extends AbstractIntegrationSpec {
     @Requires(TestPrecondition.MANDATORY_FILE_LOCKING)
     def "clean failure message indicates file"() {
         given:
-        executer.mustFork = true
+        executer.withForkingExecuter()
         buildFile << """
             apply plugin: 'base'
         """
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
index 849a049..486db0b 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
@@ -17,7 +17,6 @@
 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 {
@@ -25,9 +24,7 @@ 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
-        }
+        distribution.requireIsolatedDaemons() // make sure we get the same daemon both times
 
         buildFile << "apply plugin: 'test-plugin'"
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.groovy
new file mode 100644
index 0000000..e9d3700
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginIntegrationTest.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.github
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class GitHubDependenciesPluginIntegrationTest extends AbstractIntegrationSpec {
+
+    // Note: this is hitting the real GitHub, so will fail if GitHub is down.
+    def "can resolve from github"() {
+        given:
+        buildFile << """
+            apply plugin: 'github-dependencies'
+
+            repositories {
+                github.downloads {
+                    user "gradleware"
+                }
+            }
+
+            configurations { deps }
+
+            dependencies {
+                deps "gradle-talks:gradle-migration at zip"
+            }
+
+            task getdeps(type: Copy) {
+                from configurations.deps
+                into "deps"
+            }
+        """
+
+        when:
+        executer.withArguments("-i", "--refresh-dependencies")
+        run "getdeps"
+
+        then:
+        file("deps/gradle-migration-.zip").exists()
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy
new file mode 100644
index 0000000..dc05c76
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.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.compile.daemon
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+class ParallelCompilerDaemonIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources resources
+
+    def "daemon compiler can handle --parallel"() {
+        generateProjects(10, 10, 10)
+
+        args("--parallel")
+
+        expect:
+        succeeds("classes")
+    }
+
+    private generateProjects(int numProjects, int numJavaSources, int numGroovySources) {
+        def settingsText = ""
+        numProjects.times { count ->
+            settingsText += "include 'project$count'\n"
+        }
+        resources.dir.file("settings.gradle") << settingsText
+
+        numProjects.times { count ->
+            def projectDir = resources.dir.createDir("project$count")
+            generateSources(numJavaSources, resources.dir.file("JavaClass.java"), new File(projectDir, "src/main/java"))
+            generateSources(numGroovySources, resources.dir.file("GroovyClass.groovy"), new File(projectDir, "src/main/groovy"))
+        }
+    }
+
+    private generateSources(int num, File originalFile, File destinationDir) {
+        def text = originalFile.text
+        def className = originalFile.name.substring(0, originalFile.name.lastIndexOf("."))
+        def extension = originalFile.name.substring(originalFile.name.lastIndexOf(".") + 1)
+
+        num.times { count ->
+            def newClassName = className + count
+            def newText = text.replace(className, newClassName)
+            def file = new File(destinationDir, "${newClassName}.${extension}")
+            file.parentFile.mkdirs()
+            file << newText
+        }
+    }
+}
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
index 5867058..6165ded 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
@@ -16,8 +16,9 @@
 package org.gradle.groovy.compile
 
 import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.util.VersionNumber
 
- at TargetVersions(['1.6.9', '1.7.10', '1.8.6'])
+ at TargetVersions(['1.6.9', '1.7.11', '1.8.8', '2.0.4'])
 class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
 
     @Override
@@ -37,7 +38,7 @@ class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationS
 
     @Override
     String getCompileErrorOutput() {
-        if (version.startsWith('1.8')) {
+        if (version == '1.7.11' || versionNumber >= VersionNumber.parse('1.8.0')) {
             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
index 7014c78..c65ec63 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
@@ -17,7 +17,9 @@ package org.gradle.groovy.compile
 
 import org.gradle.integtests.fixtures.TargetVersions
 
- at TargetVersions(['1.6.9', '1.7.10', '1.8.6'])
+// Note that canCompileAgainstGroovyClassThatDependsOnExternalClass() isn't executed
+// for all Groovy versions (but still shown as green). See the (inherited) test method for details.
+ at TargetVersions(['1.6.9', '1.7.10', '1.7.11' , '1.8.6', '1.8.8', '2.0.5'])
 class AntInProcessGroovyCompilerIntegrationTest extends BasicGroovyCompilerIntegrationSpec {
     def setup() {
         executer.withForkingExecuter()
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
index c1decd7..88f31a5 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
@@ -18,12 +18,14 @@ 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
+import org.gradle.util.VersionNumber
+import org.junit.Rule
+
 import com.google.common.collect.Ordering
 
- at TargetVersions(['1.5.8', '1.6.9', '1.7.10', '1.8.6', '2.0.0'])
+ at TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.4'])
 abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
     @Rule TestResources resources = new TestResources()
 
@@ -55,6 +57,11 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     }
 
     def "canCompileAgainstGroovyClassThatDependsOnExternalClass"() {
+        if (getClass() == AntInProcessGroovyCompilerIntegrationTest &&
+                (version == '1.7.11' || versionNumber >= VersionNumber.parse('1.8.7'))) {
+            return // known not to work; see comment on GRADLE-2404
+        }
+
         when:
         run("test")
 
@@ -87,23 +94,15 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     }
 
     private void tweakBuildFile() {
-        if (version == "2.0.0") {
-            // groovy-all-2.0.0 isn't compatible with JDK 1.5
-            // see GROOVY-5591
-            buildFile << """
-dependencies {
-    groovy 'org.codehaus.groovy:groovy:$version'
-    groovy 'org.codehaus.groovy:groovy-test:$version'
-}
-            """
-        } else {
-            buildFile << """
+        buildFile << """
 dependencies { groovy 'org.codehaus.groovy:groovy-all:$version' }
-            """
-        }
+        """
 
-        buildFile << compilerConfiguration()
-        println "->> USING BUILD FILE: ${buildFile.text}"
+        buildFile << """
+DeprecationLogger.whileDisabled {
+    ${compilerConfiguration()}
+}
+        """
     }
 
     abstract String compilerConfiguration()
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
index 5de4e6b..deafeb4 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
@@ -16,15 +16,8 @@
 package org.gradle.groovy.compile
 
 class DaemonGroovyCompilerIntegrationTest extends ApiGroovyCompilerIntegrationSpec {
-
     @Override
     String compilerConfiguration() {
-'''
-    tasks.withType(GroovyCompile) {
-        groovyOptions.useAnt = false
-        groovyOptions.fork = true
-    }
-'''
+        "tasks.withType(GroovyCompile) { groovyOptions.fork = true }"
     }
-
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
index 053b200..e6c2db0 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
@@ -21,7 +21,7 @@ import org.gradle.util.TestPrecondition
 import org.gradle.integtests.fixtures.TargetVersions
 
 @Requires(TestPrecondition.JDK7)
- at TargetVersions(['2.0.0-beta-3:indy'])
+ at TargetVersions(['2.0.4:indy'])
 class InvokeDynamicGroovyCompilerSpec extends ApiGroovyCompilerIntegrationSpec {
     def canEnableAndDisableInvokeDynamicOptimization() {
         when:
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
index d4d5a6f..6061771 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
@@ -25,11 +25,11 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
 class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
 
-    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @IgnoreIf({ AvailableJavaHomes.bestJre == null})
     @Unroll
     def "groovy java cross compilation works in forking mode = #forkMode and useAnt = #useAnt when JAVA_HOME is set to JRE"() {
         given:
-        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        def jreJavaHome = AvailableJavaHomes.bestJre
         writeJavaTestSource("src/main/groovy")
         writeGroovyTestSource("src/main/groovy")
         file('build.gradle') << """
@@ -40,8 +40,7 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
                 }
                 compileGroovy{
                     options.fork = ${forkMode}
-                    options.useAnt = ${useAnt}
-                    groovyOptions.useAnt = ${useAnt}
+                    DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
                 }
                 """
         when:
@@ -56,6 +55,7 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
     }
 
     @Requires(TestPrecondition.WINDOWS)
+    @Unroll
     def "groovy compiler works when gradle is started with no JAVA_HOME defined in forking mode = #forkMode and useAnt = #useAnt"() {
         given:
         writeJavaTestSource("src/main/groovy")
@@ -67,8 +67,10 @@ class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
             }
             compileGroovy{
                 options.fork = ${forkMode}
-                options.useAnt = ${useAnt}
-                groovyOptions.useAnt = ${useAnt}
+                DeprecationLogger.whileDisabled {
+                    options.useAnt = ${useAnt}
+                    groovyOptions.useAnt = ${useAnt}
+                }
             }
             """
         when:
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
index 4e6f105..641be7e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
@@ -18,12 +18,17 @@
 package org.gradle.java.compile
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ClassFile
 
 abstract class BasicJavaCompilerIntegrationSpec extends AbstractIntegrationSpec {
     def setup() {
         executer.withArguments("-i")
         buildFile << buildScript()
-        buildFile << compilerConfiguration()
+        buildFile << """
+DeprecationLogger.whileDisabled {
+    ${compilerConfiguration()}
+}
+"""
     }
 
     def compileGoodCode() {
@@ -191,7 +196,7 @@ class Main {
 
     def badCode() {
         file("src/main/java/compile/test/Person.java") << '''
-        package compile.fork;
+        package compile.test;
 
         public class Person {
             String name;
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
deleted file mode 100644
index 4b68225..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/ClassFile.groovy
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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/JavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
index 763b147..c2e6e1d 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
@@ -44,7 +44,7 @@ abstract class JavaCompilerIntegrationSpec extends BasicJavaCompilerIntegrationS
                 memoryInitialSize = '64m'
                 memoryMaximumSize = '128m'
             }
-'''
+        '''
 
         expect:
         succeeds("compileJava")
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
index 692f8d2..d9f6a1a 100644
--- 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
@@ -19,12 +19,7 @@ import org.gradle.java.compile.JavaCompilerIntegrationSpec
 
 class DaemonJavaCompilerIntegrationTest extends JavaCompilerIntegrationSpec {
     def compilerConfiguration() {
-        '''
-compileJava.options.with {
-    useAnt = false
-    fork = true
-}
-'''
+        "tasks.withType(JavaCompile) { options.fork = true }"
     }
 
     def logStatement() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
index 35507c3..1014e0b 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
@@ -25,18 +25,18 @@ import spock.lang.Unroll
 
 class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
 
-    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @IgnoreIf({ AvailableJavaHomes.bestJre == null})
     @Unroll
     def "java compilation works in forking mode = #forkMode and useAnt = #useAnt when JAVA_HOME is set to JRE"() {
         given:
-        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        def jreJavaHome = AvailableJavaHomes.bestJre
         writeJavaTestSource("src/main/java");
         file('build.gradle') << """
         println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
         apply plugin:'java'
         compileJava{
             options.fork = ${forkMode}
-            options.useAnt = ${useAnt}
+            DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
         }
         """
         when:
@@ -50,6 +50,7 @@ class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
     }
 
     @Requires(TestPrecondition.WINDOWS)
+    @Unroll
     def "java compilation works in forking mode = #forkMode and useAnt = #useAnt when gradle is started with no JAVA_HOME defined"() {
         given:
         writeJavaTestSource("src/main/java");
@@ -57,7 +58,7 @@ class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
                     apply plugin:'java'
                     compileJava{
                         options.fork = ${forkMode}
-                        options.useAnt = ${useAnt}
+                        DeprecationLogger.whileDisabled { options.useAnt = ${useAnt} }
         }
         """
         def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
index 5c84702..a495b93 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
 import spock.lang.Issue
 import spock.lang.Timeout
 import spock.lang.Unroll
@@ -34,7 +35,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
             repositories { mavenCentral() }
             dependencies { testCompile "junit:junit:4.8.2" }
         """
-        
+
         and:
         file("src/test/java/SomeTest.java") << """
             import org.junit.*;
@@ -45,10 +46,10 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
                 }
             }
         """
-        
+
         when:
         run "test"
-        
+
         then:
         ":test" in nonSkippedTasks
     }
@@ -79,4 +80,53 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         "useJUnit"  | "junit:junit:4.10"        | "org.junit.runner.Result"
         "useTestNG" | "org.testng:testng:6.3.1" | "org.testng.Converter"
     }
+
+    @Timeout(30)
+    @Issue("http://issues.gradle.org/browse/GRADLE-2527")
+    def "test class detection works for custom test tasks"() {
+        given:
+        buildFile << """
+                apply plugin:'java'
+                repositories{ mavenCentral() }
+
+                sourceSets{
+	                othertests{
+		                java.srcDir file('src/othertests/java')
+	                    resources.srcDir file('src/othertests/resources')
+	                }
+                }
+
+                dependencies{
+	                othertestsCompile "junit:junit:4.10"
+                }
+
+                task othertestsTest(type:Test){
+	                useJUnit()
+	                classpath = sourceSets.othertests.runtimeClasspath
+	                testClassesDir = sourceSets.othertests.output.classesDir
+	            }
+            """
+
+        and:
+        file("src/othertests/java/AbstractTestClass.java") << """
+                import junit.framework.TestCase;
+                public abstract class AbstractTestClass extends TestCase {
+                }
+            """
+
+        file("src/othertests/java/TestCaseExtendsAbstractClass.java") << """
+                import junit.framework.Assert;
+                public class TestCaseExtendsAbstractClass extends AbstractTestClass{
+                    public void testTrue() {
+                        Assert.assertTrue(true);
+                    }
+                }
+            """
+
+        when:
+        run "othertestsTest"
+        then:
+        def result = new JUnitTestExecutionResult(distribution.testDir)
+        result.assertTestClassesExecuted("TestCaseExtendsAbstractClass")
+    }
 }
\ No newline at end of file
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
index a6fd905..75b0626 100755
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
@@ -16,13 +16,14 @@
 package org.gradle.testing.junit
 
 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.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
-import org.junit.Before
 
 public class JUnitIntegrationTest extends AbstractIntegrationTest {
     @Rule public final TestResources resources = new TestResources()
@@ -89,12 +90,16 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
         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()
+        result.testClass('org.gradle.Junit3Test')
+                .assertTestCount(1, 0, 0)
+                .assertTestsExecuted('testRenamesItself')
+                .assertTestPassed('testRenamesItself')
+        result.testClass('org.gradle.Junit4Test')
+                .assertTestCount(2, 0, 0)
+                .assertTestsExecuted('ok')
+                .assertTestPassed('ok')
+                .assertTestsSkipped('broken')
+        result.testClass('org.gradle.IgnoredTest').assertTestCount(0, 0, 0).assertTestsExecuted()
     }
 
     @Test
@@ -145,8 +150,10 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 '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.BrokenTest')
+                .assertTestCount(2, 2, 0)
+                .assertTestFailed('failure', equalTo('java.lang.AssertionError: failed'))
+                .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'))
@@ -415,4 +422,22 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         result.testClass('org.gradle.Test1').assertTestPassed('ok')
         result.testClass('org.gradle.Test2').assertTestPassed('ok')
     }
+
+    // primarily tests that we don't crash like we used to
+    @Test
+    void canHandleMultipleThreadsWritingToSystemOut() {
+        def result = executer.withTasks("test").run()
+        assert result.getOutput().contains("thread 0 out")
+        assert result.getOutput().contains("thread 1 out")
+        assert result.getOutput().contains("thread 2 out")
+    }
+
+    // primarily tests that we don't crash like we used to
+    @Test
+    void canHandleMultipleThreadsWritingToSystemErr() {
+        def result = executer.withTasks("test").run()
+        assert result.getOutput().contains("thread 0 out")
+        assert result.getOutput().contains("thread 1 out")
+        assert result.getOutput().contains("thread 2 out")
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
new file mode 100644
index 0000000..edf6290
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
@@ -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.testing.testng
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import spock.lang.Unroll
+
+import static org.hamcrest.Matchers.*
+import static org.hamcrest.core.IsNot.not
+
+public class TestNGProducesJUnitXmlResultsIntegrationTest extends
+        AbstractIntegrationSpec {
+    def setup() {
+        executer.allowExtraLogging = false
+    }
+
+    @Unroll("#testConfiguration")
+    def "produces JUnit xml results"() {
+        expect:
+        assertProducesXmlResults(testConfiguration)
+
+        where:
+        testConfiguration << [
+                "useTestNG()",
+                "useTestNG(); forkEvery 1",
+                "useTestNG(); maxParallelForks 2"
+        ]
+    }
+
+    def assertProducesXmlResults(String testConfiguration) {
+        file("src/test/java/org/MixedMethodsTest.java") << """package org;
+import org.testng.*;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class MixedMethodsTest {
+    @Test public void passing() {
+        System.out.println("out.pass");
+        System.err.println("err.pass");
+    }
+    @Test public void failing() {
+        System.out.println("out.fail");
+        System.err.println("err.fail");
+        fail("failing!");
+    }
+    @Test public void passing2() {
+        System.out.println("out.pass2");
+        System.err.println("err.pass2");
+    }
+    @Test public void failing2() {
+        System.out.println("out.fail2");
+        System.err.println("err.fail2");
+        fail("failing2!");
+    }
+    @Test(enabled = false) public void skipped() {}
+}
+"""
+        file("src/test/java/org/PassingTest.java") << """package org;
+import org.testng.*;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class PassingTest {
+    @Test public void passing() {
+        System.out.println("out" );
+    }
+    @Test public void passing2() {}
+}
+"""
+        file("src/test/java/org/FailingTest.java") << """package org;
+import org.testng.*;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class FailingTest {
+    @Test public void failing() {
+        System.err.println("err");
+        fail();
+    }
+    @Test public void failing2() {
+        fail();
+    }
+}
+"""
+        file("src/test/java/org/NoOutputsTest.java") << """package org;
+import org.testng.*;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class NoOutputsTest {
+    @Test(enabled=false) public void skipped() {}
+    @Test public void passing() {}
+}
+"""
+
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+
+test {
+    testReport = true
+    $testConfiguration
+}
+"""
+        //when
+        executer.withTasks('test').runWithFailure()
+
+        //then
+        def junitResult = new JUnitTestExecutionResult(file("."));
+        junitResult
+            .assertTestClassesExecuted("org.FailingTest","org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest")
+
+        junitResult.testClass("org.MixedMethodsTest")
+            .assertTestCount(4, 2, 0)
+            .assertTestsExecuted("passing", "passing2", "failing", "failing2")
+            .assertTestFailed("failing", equalTo('java.lang.AssertionError: failing!'))
+            .assertTestFailed("failing2", equalTo('java.lang.AssertionError: failing2!'))
+            .assertTestPassed("passing")
+            .assertTestPassed("passing2")
+            .assertTestsSkipped()
+            .assertStderr(allOf(containsString("err.fail"), containsString("err.fail2"), containsString("err.pass"), containsString("err.pass2")))
+            .assertStderr(not(containsString("out.")))
+            .assertStdout(allOf(containsString("out.fail"), containsString("out.fail2"), containsString("out.pass"), containsString("out.pass2")))
+            .assertStdout(not(containsString("err.")))
+
+        junitResult.testClass("org.PassingTest")
+            .assertTestCount(2, 0, 0)
+            .assertTestsExecuted("passing", "passing2")
+            .assertTestPassed("passing").assertTestPassed("passing2")
+            .assertStdout(equalTo("out\n"))
+            .assertStderr(equalTo(""))
+
+        junitResult.testClass("org.FailingTest")
+            .assertTestCount(2, 2, 0)
+            .assertTestsExecuted("failing", "failing2")
+            .assertTestFailed("failing", anything()).assertTestFailed("failing2", anything())
+            .assertStdout(equalTo(""))
+            .assertStderr(equalTo("err\n"))
+
+        junitResult.testClass("org.NoOutputsTest")
+            .assertTestCount(1, 0, 0)
+            .assertTestsExecuted("passing").assertTestPassed("passing")
+            .assertStdout(equalTo(""))
+            .assertStderr(equalTo(""))
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
new file mode 100644
index 0000000..33098d1
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.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.testing.testng
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.TestNGExecutionResult
+
+public class TestNGProducesOldReportsIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        executer.allowExtraLogging = false
+    }
+
+    def "produces only the old reports by default"() {
+        given:
+        file("src/test/java/org/MixedMethodsTest.java") << """package org;
+import org.testng.*;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class MixedMethodsTest {
+    @Test public void passing() {
+    }
+    @Test public void failing() {
+        fail("failing!");
+    }
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+
+test {
+    useTestNG()
+}
+"""
+        when:
+        executer.withTasks('test').runWithFailure()
+
+        then:
+        !new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
+
+        def testNG = new TestNGExecutionResult(file("."))
+        testNG.hasTestNGXmlResults()
+        testNG.hasHtmlResults()
+        testNG.hasJUnitResultsGeneratedByTestNG()
+    }
+
+    def "can generate only the new reports"() {
+        given:
+        file("src/test/java/org/SomeTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class SomeTest {
+    @Test public void passing() {}
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+test {
+  testReport = true
+  useTestNG()
+}
+"""
+        when:
+        executer.withTasks('test').run()
+
+        then:
+        new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
+
+        def testNG = new TestNGExecutionResult(file("."))
+        !testNG.hasTestNGXmlResults()
+        !testNG.hasJUnitResultsGeneratedByTestNG()
+        testNG.hasHtmlResults()
+    }
+
+    def "can prevent generating the old and new reports"() {
+        given:
+        file("src/test/java/org/SomeTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class SomeTest {
+    @Test public void passing() {}
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+test {
+  useTestNG {
+    useDefaultListeners = false
+    testReport = false
+  }
+}
+"""
+        when:
+        executer.withTasks('test').run()
+
+        then:
+        !new JUnitTestExecutionResult(file(".")).hasJUnitXmlResults()
+
+        def testNG = new TestNGExecutionResult(file("."))
+        !testNG.hasTestNGXmlResults()
+        !testNG.hasHtmlResults()
+        !testNG.hasJUnitResultsGeneratedByTestNG()
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/GroovyClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/GroovyClass.groovy
new file mode 100644
index 0000000..3783b28
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/GroovyClass.groovy
@@ -0,0 +1,96 @@
+/**
+ * An immutable classpath.
+ */
+public class GroovyClass implements Serializable {
+    private final List<File> files;
+
+    public GroovyClass(Iterable<File> files) {
+        this.files = new ArrayList<File>();
+        for (File file : files) {
+            this.files.add(file);
+        }
+    }
+
+    public GroovyClass(File... files) {
+        this(Arrays.asList(files));
+    }
+
+    @Override
+    public String toString() {
+        return files.toString();
+    }
+
+    public boolean isEmpty() {
+        return files.isEmpty();
+    }
+
+    public Collection<URI> getAsURIs() {
+        List<URI> urls = new ArrayList<URI>();
+        for (File file : files) {
+            urls.add(file.toURI());
+        }
+        return urls;
+    }
+
+    public Collection<File> getAsFiles() {
+        return files;
+    }
+
+    public URL[] getAsURLArray() {
+        Collection<URL> result = getAsURLs();
+        return result.toArray(new URL[result.size()]);
+    }
+
+    public Collection<URL> getAsURLs() {
+        List<URL> urls = new ArrayList<URL>();
+        for (File file : files) {
+            try {
+                urls.add(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return urls;
+    }
+
+    public GroovyClass plus(GroovyClass other) {
+        if (files.isEmpty()) {
+            return other;
+        }
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new GroovyClass(concat(files, other.getAsFiles()));
+    }
+
+    public GroovyClass plus(Collection<File> other) {
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new GroovyClass(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;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        GroovyClass other = (GroovyClass) obj;
+        return files.equals(other.files);
+    }
+
+    @Override
+    public int hashCode() {
+        return files.hashCode();
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/JavaClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/JavaClass.java
new file mode 100644
index 0000000..2f26c2b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/JavaClass.java
@@ -0,0 +1,106 @@
+import java.io.File;
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An immutable classpath.
+ */
+public class JavaClass implements Serializable {
+    private final List<File> files;
+
+    public JavaClass(Iterable<File> files) {
+        this.files = new ArrayList<File>();
+        for (File file : files) {
+            this.files.add(file);
+        }
+    }
+
+    public JavaClass(File... files) {
+        this(Arrays.asList(files));
+    }
+
+    @Override
+    public String toString() {
+        return files.toString();
+    }
+
+    public boolean isEmpty() {
+        return files.isEmpty();
+    }
+
+    public Collection<URI> getAsURIs() {
+        List<URI> urls = new ArrayList<URI>();
+        for (File file : files) {
+            urls.add(file.toURI());
+        }
+        return urls;
+    }
+
+    public Collection<File> getAsFiles() {
+        return files;
+    }
+
+    public URL[] getAsURLArray() {
+        Collection<URL> result = getAsURLs();
+        return result.toArray(new URL[result.size()]);
+    }
+
+    public Collection<URL> getAsURLs() {
+        List<URL> urls = new ArrayList<URL>();
+        for (File file : files) {
+            try {
+                urls.add(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return urls;
+    }
+
+    public JavaClass plus(JavaClass other) {
+        if (files.isEmpty()) {
+            return other;
+        }
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new JavaClass(concat(files, other.getAsFiles()));
+    }
+
+    public JavaClass plus(Collection<File> other) {
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new JavaClass(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;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        JavaClass other = (JavaClass) obj;
+        return files.equals(other.files);
+    }
+
+    @Override
+    public int hashCode() {
+        return files.hashCode();
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..0aebc65
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest/shared/build.gradle
@@ -0,0 +1,14 @@
+subprojects {
+    apply plugin: "groovy"
+
+    dependencies {
+        groovy localGroovy()
+    }
+
+    compileJava.options.fork = true
+
+    // force creation of multiple daemons for Java compilation by alternating between two distinct sets of JVM args
+    def count = (project.name - "project") as int
+    compileJava.options.forkOptions.jvmArgs = count % 2 ? ["-dsa"] : ["-esa"]
+}
+
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle
new file mode 100644
index 0000000..7358ac5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'groovy'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+dependencies { testCompile 'junit:junit:4.8.2'}
+test {
+    testLogging {
+        showStandardStreams = true
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.groovy
new file mode 100644
index 0000000..2a93dc8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemErr/src/test/groovy/org/gradle/SystemErrTest.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
+
+public class SystemErrTest {
+    @org.junit.Test
+    void test() {
+        System.err.println ("thread 0 out")
+        def thread1 = Thread.start {
+            System.err.println "thread 1 out"
+        }
+        def thread2 = Thread.start {
+            System.err.println "thread 2 out"
+        }
+        thread1.join()
+        thread2.join()
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle
new file mode 100644
index 0000000..7358ac5
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'groovy'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+dependencies { testCompile 'junit:junit:4.8.2'}
+test {
+    testLogging {
+        showStandardStreams = true
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/src/test/groovy/org/gradle/SystemOutTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/src/test/groovy/org/gradle/SystemOutTest.groovy
new file mode 100644
index 0000000..21a1f32
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHandleMultipleThreadsWritingToSystemOut/src/test/groovy/org/gradle/SystemOutTest.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
+
+public class SystemOutTest {
+    @org.junit.Test
+    void test() {
+        println ("thread 0 out")
+        def thread1 = Thread.start {
+            println "thread 1 out"
+        }
+        def thread2 = Thread.start {
+            println "thread 2 out"
+        }
+        thread1.join()
+        thread2.join()
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
index 937c489..6a062b2 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile "junit:junit:4.10"
 }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
index a3767ea..0dfc0d3 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -21,7 +21,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile "junit:junit:4.10"
 }
 
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
index a3a56e6..b23bc4b 100644
--- 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
@@ -7,7 +7,7 @@ repositories {
 }
 
 dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.8.4"
+	groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile 'org.testng:testng:6.3.1'
 }
 
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
index a3a56e6..b23bc4b 100644
--- 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
@@ -7,7 +7,7 @@ repositories {
 }
 
 dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.8.4"
+	groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile 'org.testng:testng:6.3.1'
 }
 
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
index 8722feb..9f5b52c 100644
--- 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
@@ -4,13 +4,17 @@ repositories {
     mavenCentral()
 }
 
+ext {
+    ngIncluded = "database"
+    ngExcluded = "slow"
+}
 dependencies {
     testCompile "org.testng:testng:6.3.1"
 }
 
 test {
     useTestNG {
-        includeGroups "database"
-        excludeGroups "slow"
+        includeGroups ngIncluded
+        excludeGroups ngExcluded
     }
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
index 05fd14b..c1bd8bb 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile "org.testng:testng:6.3.1"
 }
 
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
index 95d4d5f..d2465c6 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -21,7 +21,7 @@ repositories {
 }
 
 dependencies {
-    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    groovy "org.codehaus.groovy:groovy-all:1.8.8"
     testCompile "org.testng:testng:6.3.1"
 }
 
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
index 4cd03e5..d13ed39 100644
--- 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
@@ -19,7 +19,7 @@ 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.
+ * The policy for which artifacts should be published by default when none are explicitly declared.
  */
 public class DefaultArtifactPublicationSet {
     private final PublishArtifactSet artifacts;
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 1d6219a..ef423c4 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
@@ -96,6 +96,10 @@ public class DefaultSourceSet implements SourceSet {
         return getTaskName("process", "resources");
     }
 
+    public String getJarTaskName() {
+        return getTaskName(null, "jar");
+    }
+
     public String getTaskName(String verb, String target) {
         if (verb == null) {
             return StringUtils.uncapitalize(String.format("%s%s", getTaskBaseName(), StringUtils.capitalize(target)));
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 24d9360..062e4c2 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
@@ -21,8 +21,7 @@ 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.CompileOptions
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
+import org.gradle.util.VersionNumber
 
 /**
  * Please note: includeAntRuntime=false is ignored if groovyc is used in non fork mode. In this case the runtime classpath is
@@ -32,8 +31,6 @@ import org.slf4j.LoggerFactory
  * @author Hans Dockter
  */
 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
 
@@ -49,10 +46,17 @@ class AntGroovyCompiler implements org.gradle.api.internal.tasks.compile.Compile
 
         // Add in commons-cli, as the Groovy POM does not (for some versions of Groovy)
         Collection antBuilderClasspath = (spec.groovyClasspath as List) + classPathRegistry.getClassPath("COMMONS_CLI").asFiles
-        
+
+        def groovyVersion = sniffGroovyVersion(spec.groovyClasspath)
+        // in Groovy 1.7.11, 1.8.7, and beyond, the combination of includeAntRuntime=false and fork=false is no longer allowed by the
+        // groovyc Ant task and fails hard. That's why we have to enforce includeAntRuntime=true whenever fork=false in these versions,
+        // even though this breaks some stuff. For example, compiling a class that extends GroovyTestCase runs into a NoClassDefFoundError:
+        // org/junit/TestCase then.
+        def includeAntRuntime = groovyVersion == VersionNumber.parse("1.7.11") || groovyVersion >= VersionNumber.parse("1.8.7") ? !spec.groovyCompileOptions.fork : false
+
         ant.withGroovy(antBuilderClasspath).execute {
             taskdef(name: 'groovyc', classname: 'org.codehaus.groovy.ant.Groovyc')
-            def task = groovyc([includeAntRuntime: false, destdir: spec.destinationDir, classpath: ((spec.classpath as List) + antBuilderClasspath).join(File.pathSeparator)]
+            def task = groovyc([includeAntRuntime: includeAntRuntime, 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)) {
@@ -77,4 +81,16 @@ class AntGroovyCompiler implements org.gradle.api.internal.tasks.compile.Compile
         }
         result
     }
+
+    private VersionNumber sniffGroovyVersion(Iterable<File> classpath) {
+        def classLoader = new URLClassLoader(classpath*.toURI()*.toURL() as URL[], (ClassLoader) null)
+        try {
+            def clazz = classLoader.loadClass("groovy.lang.GroovySystem")
+            return VersionNumber.parse(clazz.getVersion())
+        } catch (ClassNotFoundException ignored) {
+            return VersionNumber.UNKNOWN
+        } catch (LinkageError ignored) {
+            return VersionNumber.UNKNOWN
+        }
+    }
 }
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
index cd9b9e0..7146cf3 100644
--- 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
@@ -41,9 +41,8 @@ public class CommandLineJavaCompilerArgumentsGenerator implements CompileSpecToA
     }
 
     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();
+        List<String> launcherOptions = new JavaCompilerArgumentsBuilder(spec).includeLauncherOptions(true).includeMainOptions(false).includeClasspath(false).build();
+        List<String> remainingArgs = new JavaCompilerArgumentsBuilder(spec).includeSourceFiles(true).build();
         Iterable<String> allArgs = Iterables.concat(launcherOptions, remainingArgs);
         if (exceedsWindowsCommandLineLengthLimit(allArgs)) {
             return Iterables.concat(launcherOptions, shortenArgs(remainingArgs));
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
index e196371..3fae6a5 100644
--- 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
@@ -23,4 +23,8 @@ public class CompilationFailedException extends RuntimeException {
     public CompilationFailedException(int exitCode) {
         super(String.format("Compilation failed with exit code %d; see the compiler error output for details.", exitCode));
     }
+
+    public CompilationFailedException(Throwable cause) {
+        super(cause);
+    }
 }
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
index caf43a3..6b64943 100644
--- 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
@@ -21,13 +21,17 @@ 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 GroovyCompileOptions compileOptions;
     private Iterable<File> groovyClasspath;
 
     public GroovyCompileOptions getGroovyCompileOptions() {
         return compileOptions;
     }
 
+    public void setGroovyCompileOptions(GroovyCompileOptions compileOptions) {
+        this.compileOptions = compileOptions;
+    }
+
     public Iterable<File> getGroovyClasspath() {
         return 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
index ce65051..b0eefd8 100644
--- 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
@@ -22,14 +22,17 @@ 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();
+    private CompileOptions compileOptions;
 
     public CompileOptions getCompileOptions() {
         return compileOptions;
     }
 
+    public void setCompileOptions(CompileOptions compileOptions) {
+        this.compileOptions = compileOptions;
+    }
+
     public File getDependencyCacheDir() {
         return dependencyCacheDir;
     }
@@ -45,12 +48,4 @@ public class DefaultJavaCompileSpec extends DefaultJvmLanguageCompileSpec implem
     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
index eeffee8..4fc2368 100644
--- 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
@@ -31,7 +31,7 @@ public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
     private final TemporaryFileProvider tempFileProvider;
     private final Factory<AntBuilder> antBuilderFactory;
     private final JavaCompilerFactory inProcessCompilerFactory;
-    private boolean groovyJointCompilation;
+    private boolean jointCompilation;
 
     public DefaultJavaCompilerFactory(ProjectInternal project, TemporaryFileProvider tempFileProvider, Factory<AntBuilder> antBuilderFactory, JavaCompilerFactory inProcessCompilerFactory){
         this.project = project;
@@ -42,11 +42,11 @@ public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
 
     /**
      * 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.
+     * together with another language's compiler in the compiler daemon.
+     * In that case, the other language's normalizing and daemon compilers should be used.
      */
-    public void setGroovyJointCompilation(boolean flag) {
-        groovyJointCompilation = flag;
+    public void setJointCompilation(boolean flag) {
+        jointCompilation = flag;
     }
 
     public Compiler<JavaCompileSpec> create(CompileOptions options) {
@@ -57,7 +57,7 @@ public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
         }
 
         Compiler<JavaCompileSpec> result = createTargetCompiler(options);
-        if (!groovyJointCompilation) {
+        if (!jointCompilation) {
             result = new NormalizingJavaCompiler(result);
         }
         return result;
@@ -78,7 +78,7 @@ public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
         }
 
         Compiler<JavaCompileSpec> compiler = inProcessCompilerFactory.create(options);
-        if (options.isFork() && !groovyJointCompilation) {
+        if (options.isFork() && !jointCompilation) {
             return new DaemonJavaCompiler(project, 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
index d59bb51..ee3d683 100644
--- 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
@@ -25,6 +25,7 @@ public class DefaultJvmLanguageCompileSpec implements JvmLanguageCompileSpec, Se
     private Iterable<File> classpath;
     private File destinationDir;
     private FileCollection source;
+    private String targetCompatibility;
 
     public File getDestinationDir() {
         return destinationDir;
@@ -50,4 +51,11 @@ public class DefaultJvmLanguageCompileSpec implements JvmLanguageCompileSpec, Se
         this.classpath = classpath;
     }
 
+    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/GroovyCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompilerFactory.java
index 2df95d4..b8990e5 100644
--- 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
@@ -25,6 +25,8 @@ 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.gradle.internal.Factory;
+import org.gradle.util.DeprecationLogger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,30 +44,34 @@ public class GroovyCompilerFactory {
         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);
-        }
+    Compiler<GroovyJavaJointCompileSpec> create(final GroovyCompileOptions groovyOptions, final CompileOptions javaOptions) {
+        return DeprecationLogger.whileDisabled(new Factory<Compiler<GroovyJavaJointCompileSpec>>() {
+            public Compiler<GroovyJavaJointCompileSpec> create() {
+                // 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);
-        }
+                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);
+                javaCompilerFactory.setJointCompilation(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/JavaCompilerArgumentsBuilder.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java
index 3cf154a..0c4048a 100644
--- 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
@@ -32,6 +32,7 @@ public class JavaCompilerArgumentsBuilder {
 
     private boolean includeLauncherOptions;
     private boolean includeMainOptions = true;
+    private boolean includeClasspath = true;
     private boolean includeSourceFiles;
 
     private List<String> args;
@@ -50,6 +51,11 @@ public class JavaCompilerArgumentsBuilder {
         return this;
     }
 
+    public JavaCompilerArgumentsBuilder includeClasspath(boolean flag) {
+        includeClasspath = flag;
+        return this;
+    }
+
     public JavaCompilerArgumentsBuilder includeSourceFiles(boolean flag) {
         includeSourceFiles = flag;
         return this;
@@ -60,6 +66,7 @@ public class JavaCompilerArgumentsBuilder {
 
         addLauncherOptions();
         addMainOptions();
+        addClasspath();
         addSourceFiles();
 
         return args;
@@ -129,14 +136,19 @@ public class JavaCompilerArgumentsBuilder {
             args.add("-extdirs");
             args.add(compileOptions.getExtensionDirs());
         }
+        if (compileOptions.getCompilerArgs() != null) {
+            args.addAll(compileOptions.getCompilerArgs());
+        }
+    }
+
+    private void addClasspath() {
+        if (!includeClasspath) { return; }
+
         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() {
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
index fba2c6c..c329b2c 100644
--- 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
@@ -32,4 +32,8 @@ public interface JvmLanguageCompileSpec extends CompileSpec {
     Iterable<File> getClasspath();
 
     void setClasspath(Iterable<File> classpath);
+
+    String getTargetCompatibility();
+
+    void setTargetCompatibility(String version);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NoOpStaleClassCleaner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NoOpStaleClassCleaner.java
new file mode 100644
index 0000000..0e91abd
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NoOpStaleClassCleaner.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.api.internal.tasks.compile;
+
+public class NoOpStaleClassCleaner extends StaleClassCleaner {
+    @Override
+    public void execute() {
+        // do nothing
+    }
+}
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
index d5fc2c2..21c75b5 100644
--- 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
@@ -22,7 +22,6 @@ import org.gradle.api.UncheckedIOException;
 import org.gradle.internal.classpath.ClassPath;
 import org.gradle.util.MutableURLClassLoader;
 import org.objectweb.asm.*;
-import org.objectweb.asm.commons.EmptyVisitor;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -81,16 +80,11 @@ class TransformingClassLoader extends MutableURLClassLoader {
         return bytes;
     }
 
-    private static class AnnotationDetector implements ClassVisitor {
+    private static class AnnotationDetector extends 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) {
+        private AnnotationDetector() {
+            super(Opcodes.ASM4);
         }
 
         public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
@@ -99,28 +93,11 @@ class TransformingClassLoader extends MutableURLClassLoader {
             }
             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 {
+    private static class TransformingAdapter extends ClassVisitor {
         public TransformingAdapter(ClassWriter classWriter) {
-            super(classWriter);
+            super(Opcodes.ASM4, classWriter);
         }
 
         @Override
@@ -131,29 +108,16 @@ class TransformingClassLoader extends MutableURLClassLoader {
             return super.visitAnnotation(desc, visible);
         }
 
-        private static class AnnotationTransformingVisitor implements AnnotationVisitor {
-            private final AnnotationVisitor annotationVisitor;
+        private static class AnnotationTransformingVisitor extends 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);
+                super(Opcodes.ASM4, annotationVisitor);
             }
 
             public AnnotationVisitor visitArray(String name) {
                 if (name.equals("classes")) {
-                    return new EmptyVisitor(){
+                    return new AnnotationVisitor(Opcodes.ASM4){
                         @Override
                         public void visit(String name, Object value) {
                             Type type = (Type) value;
@@ -161,7 +125,7 @@ class TransformingClassLoader extends MutableURLClassLoader {
                         }
                     };
                 } else if (name.equals("value")) {
-                    return new EmptyVisitor() {
+                    return new AnnotationVisitor(Opcodes.ASM4) {
                         @Override
                         public void visit(String name, Object value) {
                             String type = (String) value;
@@ -169,19 +133,19 @@ class TransformingClassLoader extends MutableURLClassLoader {
                         }
                     };
                 } else {
-                    return annotationVisitor.visitArray(name);
+                    return super.visitArray(name);
                 }
             }
 
             public void visitEnd() {
                 if (!names.isEmpty()) {
-                    AnnotationVisitor visitor = annotationVisitor.visitArray("value");
+                    AnnotationVisitor visitor = super.visitArray("value");
                     for (String name : names) {
                         visitor.visit(null, name);
                     }
                     visitor.visitEnd();
                 }
-                annotationVisitor.visitEnd();
+                super.visitEnd();
             }
         }
     }
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
index 9c07da1..072f8b9 100644
--- 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
@@ -15,30 +15,44 @@
  */
 package org.gradle.api.internal.tasks.compile.daemon;
 
+import net.jcip.annotations.ThreadSafe;
+
 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 org.gradle.process.internal.WorkerProcess;
 
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
+ at ThreadSafe
 public class CompilerDaemonClient implements CompilerDaemon, CompilerDaemonClientProtocol, Stoppable {
     private final DaemonForkOptions forkOptions;
+    private final WorkerProcess workerProcess;
     private final CompilerDaemonServerProtocol server;
     private final BlockingQueue<CompileResult> compileResults = new SynchronousQueue<CompileResult>();
+    private final Lock lock = new ReentrantLock(true);
 
-    public CompilerDaemonClient(DaemonForkOptions forkOptions, CompilerDaemonServerProtocol server) {
+    public CompilerDaemonClient(DaemonForkOptions forkOptions, WorkerProcess workerProcess, CompilerDaemonServerProtocol server) {
         this.forkOptions = forkOptions;
+        this.workerProcess = workerProcess;
         this.server = server;
     }
 
     public <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T spec) {
-        server.execute(compiler, spec);
+        // currently we just allow a single compilation thread at a time (per compiler daemon)
+        // one problem to solve when allowing multiple threads is how to deal with memory requirements specified by compile tasks
+        lock.lock();
         try {
+            server.execute(compiler, spec);
             return compileResults.take();
         } catch (InterruptedException e) {
             throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            lock.unlock();
         }
     }
 
@@ -47,7 +61,13 @@ public class CompilerDaemonClient implements CompilerDaemon, CompilerDaemonClien
     }
 
     public void stop() {
-        server.stop();
+        lock.lock();
+        try {
+            server.stop();
+            workerProcess.waitForStop();
+        } finally {
+            lock.unlock();
+        }
     }
 
     public void executed(CompileResult result) {
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
index e29882b..99c2af6 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.tasks.compile.daemon;
 
-import net.jcip.annotations.NotThreadSafe;
+import net.jcip.annotations.ThreadSafe;
 
 import org.gradle.BuildAdapter;
 import org.gradle.BuildResult;
@@ -28,50 +28,59 @@ import org.gradle.process.internal.WorkerProcess;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Controls the lifecycle of the compiler daemon and provides access to it.
  */
- at NotThreadSafe
+ at ThreadSafe
 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;
-    
+    private final List<CompilerDaemonClient> clients = new ArrayList<CompilerDaemonClient>();
+
     public static CompilerDaemonManager getInstance() {
         return INSTANCE;
     }
-    
-    public CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
-        if (client != null && !client.isCompatibleWith(forkOptions)) {
-            stop();
+
+    public synchronized CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
+        if (clients.isEmpty()) {
+            registerStopOnBuildFinished(project);
         }
-        if (client == null) {
-            startDaemon(project, forkOptions);
-            stopDaemonOnceBuildFinished(project);
+
+        for (CompilerDaemonClient client: clients) {
+            if (client.isCompatibleWith(forkOptions)) {
+                return client;
+            }
         }
+
+        CompilerDaemonClient client = startDaemon(project, forkOptions);
+        clients.add(client);
         return client;
     }
-    
-    public void stop() {
-        if (client == null) {
-            return;
-        }
-
-        LOGGER.info("Stopping Gradle compiler daemon.");
 
-        client.stop();
-        client = null;
-        process.waitForStop();
-        process = null;
+    public synchronized void stop() {
+        LOGGER.info("Stopping {} Gradle compiler daemon(s).", clients.size());
+        for (CompilerDaemonClient client : clients) {
+            client.stop();
+        }
+        LOGGER.info("Stopped {} Gradle compiler daemon(s).", clients.size());
+        clients.clear();
+    }
 
-        LOGGER.info("Gradle compiler daemon stopped.");
+    private void registerStopOnBuildFinished(ProjectInternal project) {
+        project.getGradle().addBuildListener(new BuildAdapter() {
+            @Override
+            public void buildFinished(BuildResult result) {
+                stop();
+            }
+        });
     }
-    
-    private void startDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
-        LOGGER.info("Starting Gradle compiler daemon.");
+
+    private CompilerDaemonClient startDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
+        LOGGER.info("Starting Gradle compiler daemon with fork options {}.", forkOptions);
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(forkOptions.toString());
         }
@@ -89,21 +98,14 @@ public class CompilerDaemonManager implements CompilerDaemonFactory {
         javaCommand.setMaxHeapSize(forkOptions.getMaxHeapSize());
         javaCommand.setJvmArgs(forkOptions.getJvmArgs());
         javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
-        process = builder.worker(new CompilerDaemonServer()).build();
+        WorkerProcess process = builder.worker(new CompilerDaemonServer()).build();
         process.start();
         CompilerDaemonServerProtocol server = process.getConnection().addOutgoing(CompilerDaemonServerProtocol.class);
-        client = new CompilerDaemonClient(forkOptions, server);
+        CompilerDaemonClient client = new CompilerDaemonClient(forkOptions, process, 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();
-            }
-        });
+        LOGGER.info("Started Gradle compiler daemon with fork options {}.", forkOptions);
+
+        return client;
     }
 }
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 ff93258..14cf28c 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
@@ -38,17 +38,16 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
     protected static final String TEST_CASE = "junit/framework/TestCase";
     protected static final String GROOVY_TEST_CASE = "groovy/util/GroovyTestCase";
 
-    private final File testClassesDirectory;
-    private final FileCollection testClasspath;
     private List<File> testClassDirectories;
     private final ClassFileExtractionManager classFileExtractionManager;
     private final Map<File, Boolean> superClasses;
     private TestClassProcessor testClassProcessor;
     private final List<String> knownTestCaseClassNames;
 
-    protected AbstractTestFrameworkDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
-        this.testClassesDirectory = testClassesDirectory;
-        this.testClasspath = testClasspath;
+    private File testClassesDirectory;
+    private FileCollection testClasspath;
+
+    protected AbstractTestFrameworkDetector(ClassFileExtractionManager classFileExtractionManager) {
         this.classFileExtractionManager = classFileExtractionManager;
         this.superClasses = new HashMap<File, Boolean>();
         this.knownTestCaseClassNames = new ArrayList<String>();
@@ -87,7 +86,10 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
         }
 
         testClassDirectories = new ArrayList<File>();
-        testClassDirectories.add(testClassesDirectory);
+
+        if (testClassesDirectory != null) {
+            testClassDirectories.add(testClassesDirectory);
+        }
         if (testClasspath != null) {
             for (File file : testClasspath) {
                 if (file.isDirectory()) {
@@ -99,6 +101,14 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
         }
     }
 
+    public void setTestClassesDirectory(File testClassesDirectory) {
+        this.testClassesDirectory = testClassesDirectory;
+    }
+
+    public void setTestClasspath(FileCollection testClasspath) {
+        this.testClasspath = testClasspath;
+    }
+
     protected TestClassVisitor classVisitor(final File testClassFile) {
         final TestClassVisitor classVisitor = createClassVisitor();
 
@@ -139,9 +149,8 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
     }
 
     /**
-     * In none super class mode a test class is published when the class is a test and it is not abstract. In super
-     * class mode it must not publish the class otherwise it will get published multiple times (for each extending
-     * class).
+     * In none super class mode a test class is published when the class is a test and it is not abstract. In super class mode it must not publish the class otherwise it will get published multiple
+     * times (for each extending class).
      */
     protected void publishTestClass(boolean isTest, TestClassVisitor classVisitor, boolean superClass) {
         if (isTest && !classVisitor.isAbstract() && !superClass) {
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 f54a0cb..ed8ea46 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,6 @@
 package org.gradle.api.internal.tasks.testing.detection;
 
 import org.gradle.api.file.FileTree;
-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;
@@ -27,6 +26,7 @@ import org.gradle.api.internal.tasks.testing.processors.RestartEveryNTestClassPr
 import org.gradle.api.internal.tasks.testing.processors.TestMainAction;
 import org.gradle.api.internal.tasks.testing.worker.ForkingTestClassProcessor;
 import org.gradle.api.tasks.testing.Test;
+import org.gradle.internal.Factory;
 import org.gradle.internal.TrueTimeProvider;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
@@ -68,6 +68,8 @@ public class DefaultTestExecuter implements TestExecuter {
         Runnable detector;
         if (testTask.isScanForTestClasses()) {
             TestFrameworkDetector testFrameworkDetector = testTask.getTestFramework().getDetector();
+            testFrameworkDetector.setTestClassesDirectory(testTask.getTestClassesDir());
+            testFrameworkDetector.setTestClasspath(testTask.getClasspath());
             detector = new DefaultTestClassScanner(testClassFiles, testFrameworkDetector, processor);
         } else {
             detector = new DefaultTestClassScanner(testClassFiles, null, processor);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java
index 9a7678c..f26fd7a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java
@@ -16,18 +16,20 @@
 
 package org.gradle.api.internal.tasks.testing.detection;
 
-import org.objectweb.asm.commons.EmptyVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
 
 /**
  * Base class for ASM test class scanners.
  *
  * @author Tom Eyckmans
  */
-public abstract class TestClassVisitor extends EmptyVisitor {
+public abstract class TestClassVisitor extends ClassVisitor {
 
     protected final TestFrameworkDetector detector;
 
     protected TestClassVisitor(TestFrameworkDetector detector) {
+        super(Opcodes.ASM4);
         if (detector == null) {
             throw new IllegalArgumentException("detector == null!");
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java
index 64bfb51..5134366 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.tasks.testing.detection;
 
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 
 import java.io.File;
@@ -26,4 +27,8 @@ public interface TestFrameworkDetector {
     void startDetection(TestClassProcessor testClassProcessor);
 
     boolean processTestClass(File testClassFile);
+
+    void setTestClassesDirectory(File testClassesDir);
+
+    void setTestClasspath(FileCollection classpath);
 }
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 b1ec1fa..a7fa601 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
@@ -15,10 +15,9 @@
  */
 package org.gradle.api.internal.tasks.testing.junit;
 
-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.gradle.api.internal.tasks.testing.detection.AbstractTestFrameworkDetector;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,8 +29,8 @@ import java.io.File;
 public class JUnitDetector extends AbstractTestFrameworkDetector<JUnitTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(JUnitDetector.class);
 
-    public JUnitDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
-        super(testClassesDirectory, testClasspath, classFileExtractionManager);
+    public JUnitDetector(ClassFileExtractionManager classFileExtractionManager) {
+        super(classFileExtractionManager);
     }
 
     protected JUnitTestClassDetecter createClassVisitor() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java
index b1e9625..62c4204 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java
@@ -20,7 +20,6 @@ import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.commons.EmptyVisitor;
 
 /**
  * @author Tom Eyckmans
@@ -35,22 +34,6 @@ class JUnitTestClassDetecter extends TestClassVisitor {
         super(detector);
     }
 
-    /**
-     * Visits the header of the class.
-     *
-     * @param version the class version.
-     * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if the class is
-     * deprecated.
-     * @param name the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}).
-     * @param signature the signature of this class. May be <tt>null</tt> if the class is not a generic one, and does
-     * not extend or implement generic classes or interfaces.
-     * @param superName the internal of name of the super class (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}). For interfaces, the super class is {@link Object}. May be <tt>null</tt>, but only for the
-     * {@link Object} class.
-     * @param interfaces the internal names of the class's interfaces (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}). May be <tt>null</tt>.
-     */
     public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
         isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
 
@@ -65,42 +48,19 @@ class JUnitTestClassDetecter extends TestClassVisitor {
         }
     }
 
-    /**
-     * Visits an annotation of the class.
-     *
-     * @param desc the class descriptor of the annotation class.
-     * @param visible <tt>true</tt> if the annotation is visible at runtime.
-     * @return a visitor to visit the annotation values, or <tt>null</tt> if this visitor is not interested in visiting
-     *         this annotation.
-     */
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
         if ("Lorg/junit/runner/RunWith;".equals(desc)) {
             test = true;
         }
 
-        return new EmptyVisitor();
+        return null;
     }
 
-    /**
-     * Visits a method of the class. This method <i>must</i> return a new {@link MethodVisitor} instance (or
-     * <tt>null</tt>) each time it is called, i.e., it should not return a previously returned visitor.
-     *
-     * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if the method is
-     * synthetic and/or deprecated.
-     * @param name the method's name.
-     * @param desc the method's descriptor (see {@link org.objectweb.asm.Type Type}).
-     * @param signature the method's signature. May be <tt>null</tt> if the method parameters, return type and
-     * exceptions do not use generic types.
-     * @param exceptions the internal names of the method's exception classes (see {@link
-     * org.objectweb.asm.Type#getInternalName() getInternalName}). May be <tt>null</tt>.
-     * @return an object to visit the byte code of the method, or <tt>null</tt> if this class visitor is not interested
-     *         in visiting the code of this method.
-     */
     public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
         if (!test) {
             return new JUnitTestMethodDetecter(this);
         } else {
-            return new EmptyVisitor();
+            return null;
         }
     }
 
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 e4632c1..b14e896 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,8 +17,6 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.Action;
-import org.gradle.internal.id.IdGenerator;
-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;
@@ -27,6 +25,8 @@ 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.internal.id.IdGenerator;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
@@ -46,7 +46,7 @@ public class JUnitTestFramework implements TestFramework {
         this.testTask = testTask;
         reporter = new DefaultTestReport();
         options = new JUnitOptions();
-        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
+        detector = new JUnitDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
index 78d97fa..ace1986 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
@@ -16,16 +16,18 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.commons.EmptyVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 /**
  * @author Tom Eyckmans
  */
-class JUnitTestMethodDetecter extends EmptyVisitor {
+class JUnitTestMethodDetecter extends MethodVisitor {
 
     private final JUnitTestClassDetecter testClassDetecter;
 
     JUnitTestMethodDetecter(JUnitTestClassDetecter testClassDetecter) {
+        super(Opcodes.ASM4);
         this.testClassDetecter = testClassDetecter;
     }
 
@@ -33,14 +35,6 @@ class JUnitTestMethodDetecter extends EmptyVisitor {
         if ("Lorg/junit/Test;".equals(desc)) {
             testClassDetecter.setTest(true);
         }
-        return new EmptyVisitor();
-    }
-
-    public AnnotationVisitor visitAnnotationDefault() {
-        return new EmptyVisitor();
-    }
-
-    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
-        return new EmptyVisitor();
+        return null;
     }
 }
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 3950523..f2da74f 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
@@ -16,150 +16,46 @@
 
 package org.gradle.api.internal.tasks.testing.junit;
 
-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.junit.result.XmlTestSuiteFactory;
+import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuite;
 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.internal.UncheckedException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.*;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.EnumMap;
-import java.util.Map;
+import java.io.File;
 
 public class JUnitXmlReportGenerator extends StateTrackingTestResultProcessor {
     private final File testResultsDir;
-    private final DocumentBuilder documentBuilder;
-    private final String hostName;
-    private Document testSuiteReport;
+    private final XmlTestSuiteFactory testSuiteFactory = new XmlTestSuiteFactory();
     private TestState testSuite;
-    private Element rootElement;
-    private Map<TestOutputEvent.Destination, StringBuilder> outputs
-            = new EnumMap<TestOutputEvent.Destination, StringBuilder>(TestOutputEvent.Destination.class);
+    private XmlTestSuite xmlTestSuite;
 
     public JUnitXmlReportGenerator(File testResultsDir) {
         this.testResultsDir = testResultsDir;
-        try {
-            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        hostName = getHostname();
     }
 
     public void output(TestDescriptor test, TestOutputEvent event) {
-        outputs.get(event.getDestination()).append(event.getMessage());
+        xmlTestSuite.addOutput(event.getDestination(), event.getMessage());
     }
 
     @Override
     protected void started(TestState state) {
         TestDescriptorInternal test = state.test;
         if (test.getName().equals(test.getClassName())) {
-            testSuiteReport = documentBuilder.newDocument();
-            rootElement = testSuiteReport.createElement("testsuite");
-            testSuiteReport.appendChild(rootElement);
-            // Add an empty properties element for compatibility
-            rootElement.appendChild(testSuiteReport.createElement("properties"));
-            outputs.put(TestOutputEvent.Destination.StdOut, new StringBuilder());
-            outputs.put(TestOutputEvent.Destination.StdErr, new StringBuilder());
+            xmlTestSuite = testSuiteFactory.create(testResultsDir, test.getClassName(), state.getStartTime());
             testSuite = state;
         }
     }
 
     @Override
     protected void completed(TestState state) {
-        String testClassName = state.test.getClassName();
-        Element element;
         if (!state.equals(testSuite)) {
-            element = testSuiteReport.createElement(state.resultType == TestResult.ResultType.SKIPPED ? "ignored-testcase" : "testcase");
-            element.setAttribute("name", state.test.getName());
-            element.setAttribute("classname", testClassName);
-            rootElement.appendChild(element);
+            xmlTestSuite.addTestCase(state.test.getName(), state.resultType, state.getExecutionTime(), state.failures);
         } else {
-            element = rootElement;
-            rootElement.setAttribute("name", testClassName);
-            rootElement.setAttribute("tests", String.valueOf(state.testCount));
-            rootElement.setAttribute("failures", String.valueOf(state.failedCount));
-            rootElement.setAttribute("errors", "0");
-            rootElement.setAttribute("timestamp", DateUtils.format(state.getStartTime(), DateUtils.ISO8601_DATETIME_PATTERN));
-            rootElement.setAttribute("hostname", hostName);
-            Element stdoutElement = testSuiteReport.createElement("system-out");
-            stdoutElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdOut)
-                    .toString()));
-            rootElement.appendChild(stdoutElement);
-            Element stderrElement = testSuiteReport.createElement("system-err");
-            stderrElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdErr)
-                    .toString()));
-            rootElement.appendChild(stderrElement);
-        }
-
-        element.setAttribute("time", String.valueOf(state.getExecutionTime() / 1000.0));
-        for (Throwable failure : state.failures) {
-            Element failureElement = testSuiteReport.createElement("failure");
-            element.appendChild(failureElement);
-            failureElement.setAttribute("message", failureMessage(failure));
-            failureElement.setAttribute("type", failure.getClass().getName());
-            failureElement.appendChild(testSuiteReport.createTextNode(stackTrace(failure)));
-        }
-
-        if (state.equals(testSuite)) {
-            File reportFile = new File(testResultsDir, "TEST-" + testClassName + ".xml");
-            try {
-                OutputStream outstr = new BufferedOutputStream(new FileOutputStream(reportFile));
-                try {
-                    new DOMElementWriter(true).write(rootElement, outstr);
-                } finally {
-                    outstr.close();
-                }
-            } catch (IOException e) {
-                throw new GradleException(String.format("Could not write test report file '%s'.", reportFile), e);
-            }
-
+            xmlTestSuite.writeSuiteData(state.getExecutionTime());
             testSuite = null;
-            outputs.clear();
-        }
-    }
-
-    private String stackTrace(Throwable throwable) {
-        try {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter writer = new PrintWriter(stringWriter);
-            throwable.printStackTrace(writer);
-            writer.close();
-            return stringWriter.toString();
-        } catch (Throwable t) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter writer = new PrintWriter(stringWriter);
-            t.printStackTrace(writer);
-            writer.close();
-            return stringWriter.toString();
-        }
-    }
-
-    private String failureMessage(Throwable throwable) {
-        try {
-            return throwable.toString();
-        } catch (Throwable t) {
-            return String.format("Could not determine failure message for exception of type %s: %s",
-                    throwable.getClass().getName(), t);
-        }
-    }
-
-    private String getHostname() {
-        try {
-            return InetAddress.getLocalHost().getHostName();
-        } catch (UnknownHostException e) {
-            return "localhost";
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java
new file mode 100644
index 0000000..41a7e68
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestNGJUnitXmlReportGenerator.java
@@ -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.internal.tasks.testing.junit;
+
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.TestStartEvent;
+import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuite;
+import org.gradle.api.internal.tasks.testing.junit.result.XmlTestSuiteFactory;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+
+import java.io.File;
+import java.util.*;
+
+public class TestNGJUnitXmlReportGenerator implements TestResultProcessor {
+
+    //TODO SF there's still duplication between JUnit / TestNG processors wrt generation of the xml results.
+    //this one probably deserves some unit testing
+
+    private final File testResultsDir;
+    private final XmlTestSuiteFactory testSuiteFactory = new XmlTestSuiteFactory();
+
+    private Map<Object, TestInfo> tests = new HashMap<Object, TestInfo>();
+    private Map<String, XmlTestSuite> testSuites = new HashMap<String, XmlTestSuite>();
+    private Map<Object, Collection<Throwable>> failures = new HashMap<Object, Collection<Throwable>>();
+
+    private TimeProvider timeProvider = new TrueTimeProvider();
+
+    public TestNGJUnitXmlReportGenerator(File testResultsDir) {
+        this.testResultsDir = testResultsDir;
+    }
+
+    class TestInfo {
+        TestDescriptorInternal test;
+        long started;
+        TestInfo(TestDescriptorInternal test, long started) {
+            this.test = test;
+            this.started = started;
+        }
+    }
+
+    public void started(TestDescriptorInternal test, TestStartEvent event) {
+        //it would be nice if we didn't have to maintain the testId->descriptor map
+        tests.put(test.getId(), new TestInfo(test, event.getStartTime()));
+        if (!test.isComposite()) { //test method
+            if (!testSuites.containsKey(test.getClassName())) {
+                testSuites.put(test.getClassName(), testSuiteFactory.create(testResultsDir, test.getClassName(), timeProvider.getCurrentTime()));
+            }
+        }
+    }
+
+    public void completed(final Object testId, final TestCompleteEvent event) {
+        final TestInfo testInfo = tests.remove(testId);
+        if (!testInfo.test.isComposite()) { //test method
+            XmlTestSuite xmlTestSuite = testSuites.get(testInfo.test.getClassName());
+            Collection<Throwable> failures = this.failures.containsKey(testId) ? this.failures.remove(testId) : Collections.<Throwable>emptySet();
+            xmlTestSuite.addTestCase(testInfo.test.getName(), event.getResultType(), event.getEndTime() - testInfo.started, failures);
+        } else if (testInfo.test.getParent() == null) {
+            for (XmlTestSuite xmlTestSuite : testSuites.values()) {
+                xmlTestSuite.writeSuiteData(0); //it's hard to reliably say when TestNG test class has finished.
+            }
+            testSuites.clear();
+        }
+    }
+
+    public void output(Object testId, TestOutputEvent event) {
+        TestInfo testInfo = tests.get(testId);
+        if (testInfo != null && testSuites.containsKey(testInfo.test.getClassName())) {
+            //if the test does not exist or suite it means it was already completed.
+            // TODO SF we should add this output to the parent class but in case of TestNG, atm we dont know what the parent class is
+            // because the start/end class events are not generated.
+            testSuites.get(testInfo.test.getClassName()).addOutput(event.getDestination(), event.getMessage());
+        }
+    }
+
+    public void failure(Object testId, Throwable failure) {
+        if (!failures.containsKey(testId)) {
+            failures.put(testId, new LinkedHashSet<Throwable>());
+        }
+        failures.get(testId).add(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 65f71be..ba9dcbd 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
@@ -42,8 +42,8 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
             tr = append(table, "tr");
             Element td = appendWithText(tr, "td", test.getName());
             td.setAttribute("class", test.getStatusClass());
-            appendWithText(td, "td", test.getFormattedDuration());
-            td = appendWithText(td, "td", test.getFormattedResultType());
+            appendWithText(tr, "td", test.getFormattedDuration());
+            td = appendWithText(tr, "td", test.getFormattedResultType());
             td.setAttribute("class", test.getStatusClass());
         }
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
index 8d653a1..368f195 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
@@ -53,10 +53,10 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
             Element td = append(tr, "td");
             td.setAttribute("class", testPackage.getStatusClass());
             appendLink(td, String.format("%s.html", testPackage.getName()), testPackage.getName());
-            appendWithText(td, "td", testPackage.getTestCount());
-            appendWithText(td, "td", testPackage.getFailureCount());
-            appendWithText(td, "td", testPackage.getFormattedDuration());
-            td = appendWithText(td, "td", testPackage.getFormattedSuccessRate());
+            appendWithText(tr, "td", testPackage.getTestCount());
+            appendWithText(tr, "td", testPackage.getFailureCount());
+            appendWithText(tr, "td", testPackage.getFormattedDuration());
+            td = appendWithText(tr, "td", testPackage.getFormattedSuccessRate());
             td.setAttribute("class", testPackage.getStatusClass());
         }
     }
@@ -76,10 +76,10 @@ class OverviewPageRenderer extends PageRenderer<AllTestResults> {
                 Element td = append(tr, "td");
                 td.setAttribute("class", testClass.getStatusClass());
                 appendLink(td, String.format("%s.html", testClass.getName()), testClass.getName());
-                appendWithText(td, "td", testClass.getTestCount());
-                appendWithText(td, "td", testClass.getFailureCount());
-                appendWithText(td, "td", testClass.getFormattedDuration());
-                td = appendWithText(td, "td", testClass.getFormattedSuccessRate());
+                appendWithText(tr, "td", testClass.getTestCount());
+                appendWithText(tr, "td", testClass.getFailureCount());
+                appendWithText(tr, "td", testClass.getFormattedDuration());
+                td = appendWithText(tr, "td", testClass.getFormattedSuccessRate());
                 td.setAttribute("class", testClass.getStatusClass());
             }
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
index 5205330..d527633 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
@@ -41,10 +41,10 @@ class PackagePageRenderer extends PageRenderer<PackageTestResults> {
             Element td = append(tr, "td");
             td.setAttribute("class", testClass.getStatusClass());
             appendLink(td, String.format("%s.html", testClass.getName()), testClass.getSimpleName());
-            appendWithText(td, "td", testClass.getTestCount());
-            appendWithText(td, "td", testClass.getFailureCount());
-            appendWithText(td, "td", testClass.getFormattedDuration());
-            td = appendWithText(td, "td", testClass.getFormattedSuccessRate());
+            appendWithText(tr, "td", testClass.getTestCount());
+            appendWithText(tr, "td", testClass.getFailureCount());
+            appendWithText(tr, "td", testClass.getFormattedDuration());
+            td = appendWithText(tr, "td", testClass.getFormattedSuccessRate());
             td.setAttribute("class", testClass.getStatusClass());
         }
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java
new file mode 100644
index 0000000..ed74e73
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuite.java
@@ -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.api.internal.tasks.testing.junit.result;
+
+import org.apache.tools.ant.util.DOMElementWriter;
+import org.apache.tools.ant.util.DateUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.api.tasks.testing.TestResult;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.*;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 10/3/12
+ */
+public class XmlTestSuite {
+
+    private final Document testSuiteReport;
+    private final Element rootElement;
+    private final String hostname;
+    private final String className;
+    private final long startTime;
+    private final Map<TestOutputEvent.Destination, StringBuffer> outputs
+            = new EnumMap<TestOutputEvent.Destination, StringBuffer>(TestOutputEvent.Destination.class);
+    private final File reportFile;
+    private long failedCount;
+    private long testCount;
+
+    public XmlTestSuite(File testResultsDir, String className, long startTime, String hostname, Document document) {
+        this.className = className;
+        this.startTime = startTime;
+        this.hostname = hostname;
+        testSuiteReport = document;
+        rootElement = testSuiteReport.createElement("testsuite");
+        testSuiteReport.appendChild(rootElement);
+        // Add an empty properties element for compatibility
+        rootElement.appendChild(testSuiteReport.createElement("properties"));
+
+        outputs.put(TestOutputEvent.Destination.StdOut, new StringBuffer());
+        outputs.put(TestOutputEvent.Destination.StdErr, new StringBuffer());
+
+        reportFile = new File(testResultsDir, "TEST-" + className + ".xml");
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public void addTestCase(String testName, TestResult.ResultType resultType, long executionTime, Collection<Throwable> failures) {
+        String testCase = resultType == TestResult.ResultType.SKIPPED ? "ignored-testcase" : "testcase";
+        Element element = testSuiteReport.createElement(testCase);
+        element.setAttribute("name", testName);
+        element.setAttribute("classname", this.className);
+        element.setAttribute("time", String.valueOf(executionTime / 1000.0));
+        if (!failures.isEmpty()) {
+            failedCount++;
+            for (Throwable failure : failures) {
+                Element failureElement = testSuiteReport.createElement("failure");
+                element.appendChild(failureElement);
+                failureElement.setAttribute("message", failureMessage(failure));
+                failureElement.setAttribute("type", failure.getClass().getName());
+                failureElement.appendChild(testSuiteReport.createTextNode(stackTrace(failure)));
+            }
+        }
+        testCount++;
+        rootElement.appendChild(element);
+    }
+
+    public void writeSuiteData(long executionTime) {
+        rootElement.setAttribute("name", this.className);
+        rootElement.setAttribute("tests", String.valueOf(this.testCount));
+        rootElement.setAttribute("failures", String.valueOf(this.failedCount));
+        rootElement.setAttribute("errors", "0");
+        rootElement.setAttribute("timestamp", DateUtils.format(this.startTime, DateUtils.ISO8601_DATETIME_PATTERN));
+        rootElement.setAttribute("hostname", this.hostname);
+        Element stdoutElement = testSuiteReport.createElement("system-out");
+        stdoutElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdOut)
+                .toString()));
+        rootElement.appendChild(stdoutElement);
+        Element stderrElement = testSuiteReport.createElement("system-err");
+        stderrElement.appendChild(testSuiteReport.createCDATASection(outputs.get(TestOutputEvent.Destination.StdErr)
+                .toString()));
+        rootElement.appendChild(stderrElement);
+        rootElement.setAttribute("time", String.valueOf(executionTime / 1000.0));
+        outputs.clear();
+
+        writeTo(reportFile);
+    }
+
+    private String stackTrace(Throwable throwable) {
+        try {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            throwable.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        } catch (Throwable t) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            t.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        }
+    }
+
+    private String failureMessage(Throwable throwable) {
+        try {
+            return throwable.toString();
+        } catch (Throwable t) {
+            return String.format("Could not determine failure message for exception of type %s: %s",
+                    throwable.getClass().getName(), t);
+        }
+    }
+
+    public void writeTo(File reportFile) {
+        try {
+            OutputStream outstr = new BufferedOutputStream(new FileOutputStream(reportFile));
+            try {
+                new DOMElementWriter(true).write(rootElement, outstr);
+            } finally {
+                outstr.close();
+            }
+        } catch (IOException e) {
+            throw new GradleException(String.format("Could not write test report file '%s'.", reportFile), e);
+        }
+    }
+
+    public void addOutput(TestOutputEvent.Destination destination, String message) {
+        outputs.get(destination).append(message);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.java
new file mode 100644
index 0000000..9e02b9d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/XmlTestSuiteFactory.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.api.internal.tasks.testing.junit.result;
+
+import org.gradle.internal.UncheckedException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class XmlTestSuiteFactory {
+    private final String hostname;
+    private final DocumentBuilder documentBuilder;
+
+    public XmlTestSuiteFactory() {
+        hostname = getHostname();
+        try {
+            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public XmlTestSuite create(File testResultsDir, String className, long startTime) {
+        return new XmlTestSuite(testResultsDir, className, startTime, hostname, documentBuilder.newDocument());
+    }
+
+    private String getHostname() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            return "localhost";
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java
index 6bb32d1..c1d9373 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java
@@ -19,7 +19,6 @@ package org.gradle.api.internal.tasks.testing.logging;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
 import org.gradle.api.Nullable;
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.tasks.testing.TestDescriptor;
 import org.gradle.api.tasks.testing.logging.TestLogEvent;
@@ -36,7 +35,7 @@ public abstract class AbstractTestLogger {
     private final StyledTextOutputFactory textOutputFactory;
     private final LogLevel logLevel;
     private final int displayGranularity;
-    private Object lastSeenTestId;
+    private TestDescriptor lastSeenTestDescriptor;
     private TestLogEvent lastSeenTestEvent;
 
     protected AbstractTestLogger(StyledTextOutputFactory textOutputFactory, LogLevel logLevel, int displayGranularity) {
@@ -51,12 +50,11 @@ public abstract class AbstractTestLogger {
 
     protected void logEvent(TestDescriptor descriptor, TestLogEvent event, @Nullable String details) {
         StyledTextOutput output = textOutputFactory.create("TestEventLogger", logLevel);
-        Object testId = ((TestDescriptorInternal) descriptor).getId();
-        if (!testId.equals(lastSeenTestId) || event != lastSeenTestEvent) {
+        if (!descriptor.equals(lastSeenTestDescriptor) || event != lastSeenTestEvent) {
             output.append(TextUtil.getPlatformLineSeparator() + getEventPath(descriptor));
             output.withStyle(getStyle(event)).println(event.toString());
         }
-        lastSeenTestId = testId;
+        lastSeenTestDescriptor = descriptor;
         lastSeenTestEvent = event;
         if (details != null) {
             output.append(TextUtil.toPlatformLineSeparators(details));
@@ -71,7 +69,7 @@ public abstract class AbstractTestLogger {
                 // This deals with the fact that in TestNG, there are no class-level events,
                 // but we nevertheless want to see the class name. We use "." rather than
                 // " > " as a separator to make it clear that the class is not a separate
-                // level. This matters when configuring min/max/displayGranularity.
+                // level. This matters when configuring granularity.
                 names.add(current.getClassName() + "." + current.getName());
             } else {
                 names.add(current.getName());
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 641145c..9784255 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
@@ -72,7 +72,7 @@ public class MaxNParallelTestClassProcessor implements TestClassProcessor {
 
     public void stop() {
         try {
-            new CompositeStoppable(processors).add(actors).add(resultProcessorActor).stop();
+            CompositeStoppable.stoppable(processors).add(actors).add(resultProcessorActor).stop();
         } catch (DispatchException e) {
             throw UncheckedException.throwAsUncheckedException(e.getCause());
         }
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 14e23e5..ae64537 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
@@ -15,7 +15,6 @@
  */
 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;
@@ -30,8 +29,8 @@ import java.io.File;
 class TestNGDetector extends AbstractTestFrameworkDetector<TestNGTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(TestNGDetector.class);
 
-    TestNGDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
-        super(testClassesDirectory, testClasspath, classFileExtractionManager);
+    TestNGDetector(ClassFileExtractionManager classFileExtractionManager) {
+        super(classFileExtractionManager);
     }
 
     protected TestNGTestClassDetecter createClassVisitor() {
@@ -39,17 +38,9 @@ class TestNGDetector extends AbstractTestFrameworkDetector<TestNGTestClassDetect
     }
 
     /**
-     * Uses a TestClassVisitor to detect whether the class in the testClassFile is a test class.
-     * <p/>
-     * If the class is not a test, this function will go up the inheritance tree to check if a
-     * parent class is a test class. First the package of the parent class is checked, if it is a java.lang or groovy.lang
-     * the class can't be a test class, otherwise the parent class is scanned.
-     * <p/>
-     * When a parent class is a test class all the extending classes are marked as test classes.
-     *
-     * @param testClassFile
-     * @param superClass
-     * @return
+     * Uses a TestClassVisitor to detect whether the class in the testClassFile is a test class. <p/> If the class is not a test, this function will go up the inheritance tree to check if a parent
+     * class is a test class. First the package of the parent class is checked, if it is a java.lang or groovy.lang the class can't be a test class, otherwise the parent class is scanned. <p/> When a
+     * parent class is a test class all the extending classes are marked as test classes.
      */
     protected boolean processTestClass(final File testClassFile, boolean superClass) {
         final TestClassVisitor classVisitor = classVisitor(testClassFile);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java
index 209cf82..61e0e5a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java
@@ -20,7 +20,6 @@ import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.commons.EmptyVisitor;
 
 /**
  * @author Tom Eyckmans
@@ -35,22 +34,6 @@ class TestNGTestClassDetecter extends TestClassVisitor {
         super(detector);
     }
 
-    /**
-     * Visits the header of the class.
-     *
-     * @param version the class version.
-     * @param access the class's access flags (see {@link org.objectweb.asm.Opcodes}). This parameter also indicates if
-     * the class is deprecated.
-     * @param name the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}).
-     * @param signature the signature of this class. May be <tt>null</tt> if the class is not a generic one, and does
-     * not extend or implement generic classes or interfaces.
-     * @param superName the internal of name of the super class (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}). For interfaces, the super class is {@link Object}. May be <tt>null</tt>, but only for the
-     * {@link Object} class.
-     * @param interfaces the internal names of the class's interfaces (see {@link org.objectweb.asm.Type#getInternalName()
-     * getInternalName}). May be <tt>null</tt>.
-     */
     public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
         isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
 
@@ -58,41 +41,18 @@ class TestNGTestClassDetecter extends TestClassVisitor {
         this.superClassName = superName;
     }
 
-    /**
-     * Visits an annotation of the class.
-     *
-     * @param desc the class descriptor of the annotation class.
-     * @param visible <tt>true</tt> if the annotation is visible at runtime.
-     * @return a visitor to visit the annotation values, or <tt>null</tt> if this visitor is not interested in visiting
-     *         this annotation.
-     */
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
         if ("Lorg/testng/annotations/Test;".equals(desc)) {
             test = true;
         }
-        return new EmptyVisitor();
+        return null;
     }
 
-    /**
-     * Visits a method of the class. This method <i>must</i> return a new {@link org.objectweb.asm.MethodVisitor}
-     * instance (or <tt>null</tt>) each time it is called, i.e., it should not return a previously returned visitor.
-     *
-     * @param access the method's access flags (see {@link org.objectweb.asm.Opcodes}). This parameter also indicates if
-     * the method is synthetic and/or deprecated.
-     * @param name the method's name.
-     * @param desc the method's descriptor (see {@link org.objectweb.asm.Type Type}).
-     * @param signature the method's signature. May be <tt>null</tt> if the method parameters, return type and
-     * exceptions do not use generic types.
-     * @param exceptions the internal names of the method's exception classes (see {@link
-     * org.objectweb.asm.Type#getInternalName() getInternalName}). May be <tt>null</tt>.
-     * @return an object to visit the byte code of the method, or <tt>null</tt> if this class visitor is not interested
-     *         in visiting the code of this method.
-     */
     public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
         if (!isAbstract && !test) {
             return new TestNGTestMethodDetecter(this);
         } else {
-            return new EmptyVisitor();
+            return null;
         }
     }
 
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
index 51de906..5dad475 100755
--- 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
@@ -17,17 +17,18 @@
 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.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.junit.TestNGJUnitXmlReportGenerator;
 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.internal.id.IdGenerator;
+import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.StandardOutputRedirector;
+import org.gradle.util.CollectionUtils;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
-import org.gradle.internal.id.IdGenerator;
 import org.gradle.util.ReflectionUtil;
 import org.testng.ITestListener;
 import org.testng.TestNG;
@@ -43,19 +44,34 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
     private final List<File> suiteFiles;
     private final IdGenerator<?> idGenerator;
     private final StandardOutputRedirector outputRedirector;
+    private final File testResultsDir;
+    private final boolean testReportOn;
     private TestNGTestResultProcessorAdapter testResultProcessor;
     private ClassLoader applicationClassLoader;
 
-    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator, StandardOutputRedirector outputRedirector) {
+    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator,
+                                    StandardOutputRedirector outputRedirector, File testResultsDir, boolean testReportOn) {
         this.testReportDir = testReportDir;
         this.options = options;
         this.suiteFiles = suiteFiles;
         this.idGenerator = idGenerator;
         this.outputRedirector = outputRedirector;
+        this.testResultsDir = testResultsDir;
+        this.testReportOn = testReportOn;
     }
 
     public void startProcessing(TestResultProcessor resultProcessor) {
-        testResultProcessor = new TestNGTestResultProcessorAdapter(new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector), idGenerator);
+        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(TestResultProcessor.class);
+        processors.add(resultProcessor);
+        if (testReportOn) {
+            //Do not generate the xml results unless the report is wanted explicitly
+            //TODO SF this check needs to be removed when new TestNG reports are turned on by default
+            processors.add(new TestNGJUnitXmlReportGenerator(testResultsDir));
+        }
+        TestResultProcessor resultProcessorChain = new CaptureTestOutputTestResultProcessor(processors.getSource(), outputRedirector);
+
+        testResultProcessor = new TestNGTestResultProcessorAdapter(resultProcessorChain, idGenerator);
+
         applicationClassLoader = Thread.currentThread().getContextClassLoader();
     }
 
@@ -80,13 +96,18 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
             /* do nothing; method has been removed in TestNG 6.3 */
         }
         if (options.getJavadocAnnotations()) {
-            testNg.setSourcePath(GUtil.join(options.getTestResources(), File.pathSeparator));
+            testNg.setSourcePath(CollectionUtils.join(File.pathSeparator, options.getTestResources()));
         }
-        testNg.setUseDefaultListeners(options.getUseDefaultListeners());
+
+        //only use the default listeners flag when testReport is off
+        //TODO SF spockify the test, add unit test coverage and inform about 'incubating' nature of the new report
+        //we should remove this complexity when new TestNG reports are turned on by default
+        testNg.setUseDefaultListeners(!testReportOn && options.getUseDefaultListeners());
+
         testNg.addListener((Object) adaptListener(testResultProcessor));
         testNg.setVerbose(0);
-        testNg.setGroups(GUtil.join(options.getIncludeGroups(), ","));
-        testNg.setExcludedGroups(GUtil.join(options.getExcludeGroups(), ","));
+        testNg.setGroups(CollectionUtils.join(",", options.getIncludeGroups()));
+        testNg.setExcludedGroups(CollectionUtils.join(",", options.getExcludeGroups()));
         for (String listenerClass : options.getListeners()) {
             try {
                 testNg.addListener(applicationClassLoader.loadClass(listenerClass).newInstance());
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 89a22ce..d6208b2 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,15 +18,19 @@ package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.Action;
 import org.gradle.api.JavaVersion;
-import org.gradle.internal.id.IdGenerator;
-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.internal.tasks.testing.junit.report.DefaultTestReport;
+import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.process.internal.WorkerProcessBuilder;
 
 import java.io.File;
@@ -38,21 +42,26 @@ import java.util.List;
  */
 public class TestNGTestFramework implements TestFramework {
 
+    private final static Logger LOG = Logging.getLogger(TestNGTestFramework.class);
+
     private TestNGOptions options;
     private TestNGDetector detector;
     private final Test testTask;
+    private TestReporter reporter;
 
     public TestNGTestFramework(Test testTask) {
         this.testTask = testTask;
         options = new TestNGOptions(testTask.getProject().getProjectDir());
         options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property("sourceCompatibility")));
-        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
+        detector = new TestNGDetector(new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
+        reporter = new DefaultTestReport();
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
         options.setTestResources(testTask.getTestSrcDirs());
         List<File> suiteFiles = options.getSuites(testTask.getTemporaryDir());
-        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), options, suiteFiles);
+        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), options, suiteFiles,
+                testTask.getTestResultsDir(), testTask.isTestReport());
     }
 
     public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
@@ -64,8 +73,15 @@ public class TestNGTestFramework implements TestFramework {
     }
 
     public void report() {
-        // TODO currently reports are always generated because the antTestNGExecute task uses the
-        // default listeners and these generate reports by default.
+        if (!testTask.isTestReport()) {
+            LOG.info("Test report disabled, omitting generation of the html test report.");
+            return;
+        }
+        // TODO SF make the logging consistent in frameworks, add coverage and spockify the test
+        LOG.info("Generating html test report...");
+        reporter.setTestReportDir(testTask.getTestReportDir());
+        reporter.setTestResultsDir(testTask.getTestResultsDir());
+        reporter.generateReport();
     }
 
     public TestNGOptions getOptions() {
@@ -84,15 +100,22 @@ public class TestNGTestFramework implements TestFramework {
         private final File testReportDir;
         private final TestNGOptions options;
         private final List<File> suiteFiles;
+        private final File testResultsDir;
+        private final boolean testReportOn;
 
-        public TestClassProcessorFactoryImpl(File testReportDir, TestNGOptions options, List<File> suiteFiles) {
+        public TestClassProcessorFactoryImpl(File testReportDir, TestNGOptions options, List<File> suiteFiles,
+                                             File testResultsDir, boolean testReportOn) {
             this.testReportDir = testReportDir;
             this.options = options;
             this.suiteFiles = suiteFiles;
+            this.testResultsDir = testResultsDir;
+            this.testReportOn = testReportOn;
         }
 
         public TestClassProcessor create(ServiceRegistry serviceRegistry) {
-            return new TestNGTestClassProcessor(testReportDir, options, suiteFiles, serviceRegistry.get(IdGenerator.class), new JULRedirector());
+            return new TestNGTestClassProcessor(testReportDir, options, suiteFiles,
+                    serviceRegistry.get(IdGenerator.class), new JULRedirector(),
+                    testResultsDir, testReportOn);
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
index 46b02a8..37580c8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
@@ -16,7 +16,8 @@
 package org.gradle.api.internal.tasks.testing.testng;
 
 import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.commons.EmptyVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -24,11 +25,12 @@ import java.util.Set;
 /**
  * @author Tom Eyckmans
  */
-class TestNGTestMethodDetecter extends EmptyVisitor {
+class TestNGTestMethodDetecter extends MethodVisitor {
     private final TestNGTestClassDetecter testClassDetecter;
     private final Set<String> testMethodAnnotations = new HashSet<String>();
 
     public TestNGTestMethodDetecter(TestNGTestClassDetecter testClassDetecter) {
+        super(Opcodes.ASM4);
         this.testClassDetecter = testClassDetecter;
         testMethodAnnotations.add("Lorg/testng/annotations/Test;");
         testMethodAnnotations.add("Lorg/testng/annotations/BeforeSuite;");
@@ -43,14 +45,6 @@ class TestNGTestMethodDetecter extends EmptyVisitor {
         if (testMethodAnnotations.contains(desc)) {
             testClassDetecter.setTest(true);
         }
-        return new EmptyVisitor();
-    }
-
-    public AnnotationVisitor visitAnnotationDefault() {
-        return new EmptyVisitor();
-    }
-
-    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
-        return new EmptyVisitor();
+        return null;
     }
 }
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 bcc6f5e..4801603 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
@@ -22,6 +22,8 @@ import org.apache.tools.ant.taskdefs.Manifest.Attribute;
 import org.apache.tools.ant.taskdefs.Manifest.Section;
 import org.apache.tools.ant.taskdefs.ManifestException;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.IoActions;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.java.archives.Attributes;
 import org.gradle.api.java.archives.ManifestMergeSpec;
@@ -158,20 +160,13 @@ public class DefaultManifest implements org.gradle.api.java.archives.Manifest {
     }
 
     public org.gradle.api.java.archives.Manifest 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();
+        IoActions.writeFile(fileResolver.resolve(path), new ErroringAction<Writer>() {
+            @Override
+            protected void doExecute(Writer writer) throws Exception {
+                writeTo(writer);
             }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        });
+        return this;
     }
 
     public List<ManifestMergeSpec> getMergeSpecs() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java
index 94bfd51..ab10950 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java
@@ -61,7 +61,7 @@ public class BasePlugin implements Plugin<Project> {
 
     private void addAssemble(Project project) {
         Task assembleTask = project.getTasks().add(ASSEMBLE_TASK_NAME);
-        assembleTask.setDescription("Assembles all Jar, War, Zip, and Tar archives.");
+        assembleTask.setDescription("Assembles the outputs of this project.");
         assembleTask.setGroup(BUILD_GROUP);
         assembleTask.dependsOn(project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).getAllArtifacts().getBuildDependencies());
     }
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 fd3c12b..4dd10a7 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
@@ -22,18 +22,22 @@ 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.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework;
 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;
-import org.gradle.api.tasks.compile.Compile;
+import org.gradle.api.tasks.compile.JavaCompile;
 import org.gradle.api.tasks.javadoc.Javadoc;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.TestDescriptor;
 import org.gradle.api.tasks.testing.TestListener;
 import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.WrapUtil;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.util.concurrent.Callable;
 
@@ -50,11 +54,18 @@ public class JavaBasePlugin implements Plugin<Project> {
     public static final String VERIFICATION_GROUP = "verification";
     public static final String DOCUMENTATION_GROUP = "documentation";
 
+    private final Instantiator instantiator;
+
+    @Inject
+    public JavaBasePlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
     public void apply(Project project) {
         project.getPlugins().apply(BasePlugin.class);
         project.getPlugins().apply(ReportingBasePlugin.class);
 
-        JavaPluginConvention javaConvention = new JavaPluginConvention(project);
+        JavaPluginConvention javaConvention = new JavaPluginConvention((ProjectInternal) project, instantiator);
         project.getConvention().getPlugins().put("java", javaConvention);
 
         configureCompileDefaults(project, javaConvention);
@@ -122,7 +133,7 @@ public class JavaBasePlugin implements Plugin<Project> {
                 });
 
                 String compileTaskName = sourceSet.getCompileJavaTaskName();
-                Compile compileJava = project.getTasks().add(compileTaskName, Compile.class);
+                JavaCompile compileJava = project.getTasks().add(compileTaskName, JavaCompile.class);
                 configureForSourceSet(sourceSet, compileJava);
 
                 Task classes = project.getTasks().add(sourceSet.getClassesTaskName());
@@ -169,8 +180,8 @@ public class JavaBasePlugin implements Plugin<Project> {
                 });
             }
         });
-        project.getTasks().withType(Compile.class, new Action<Compile>() {
-            public void execute(final Compile compile) {
+        project.getTasks().withType(JavaCompile.class, new Action<JavaCompile>() {
+            public void execute(final JavaCompile compile) {
                 ConventionMapping conventionMapping = compile.getConventionMapping();
                 conventionMapping.map("dependencyCacheDir", new Callable<Object>() {
                     public Object call() throws Exception {
@@ -301,7 +312,7 @@ public class JavaBasePlugin implements Plugin<Project> {
         return value;
     }
 
-    private void configureTestDefaults(Test test, Project project, final JavaPluginConvention convention) {
+    private void configureTestDefaults(final Test test, Project project, final JavaPluginConvention convention) {
         test.getConventionMapping().map("testResultsDir", new Callable<Object>() {
             public Object call() throws Exception {
                 return convention.getTestResultsDir();
@@ -313,5 +324,11 @@ public class JavaBasePlugin implements Plugin<Project> {
             }
         });
         test.workingDir(project.getProjectDir());
+        //TODO SF move coverage from below to the JavaBasePluginSpec
+        test.getConventionMapping().map("testReport", new Callable<Object>() {
+            public Object call() throws Exception {
+                return !(test.getTestFramework() instanceof TestNGTestFramework);
+            }
+        });
     }
 }
\ No newline at end of file
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 1221624..8e982c3 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
@@ -98,7 +98,6 @@ public class JavaPlugin implements Plugin<Project> {
     }
 
     private void configureArchives(final Project project, final JavaPluginConvention pluginConvention) {
-        project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(TEST_TASK_NAME);
         Jar jar = project.getTasks().add(JAR_TASK_NAME, Jar.class);
         jar.getManifest().from(pluginConvention.getManifest());
         jar.setDescription("Assembles a jar archive containing the main classes.");
@@ -123,7 +122,7 @@ public class JavaPlugin implements Plugin<Project> {
 
     private void configureTest(final Project project, final JavaPluginConvention pluginConvention) {
         project.getTasks().withType(Test.class, new Action<Test>() {
-            public void execute(Test test) {
+            public void execute(final Test test) {
                 test.getConventionMapping().map("testClassesDir", new Callable<Object>() {
                     public Object call() throws Exception {
                         return pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDir();
@@ -143,6 +142,7 @@ public class JavaPlugin implements Plugin<Project> {
             }
         });
         Test test = project.getTasks().add(TEST_TASK_NAME, Test.class);
+        project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(test);
         test.setDescription("Runs the unit tests.");
         test.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
     }
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 86ef37b..d8bfbb7 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
@@ -16,9 +16,7 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.JavaVersion
-import org.gradle.api.Project
 import org.gradle.api.file.SourceDirectorySet
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.Manifest
@@ -26,6 +24,7 @@ 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.internal.reflect.Instantiator
 import org.gradle.util.ConfigureUtil
 
 /**
@@ -74,9 +73,8 @@ class JavaPluginConvention {
     @Deprecated
     DefaultManifest manifest
 
-    JavaPluginConvention(Project project) {
+    JavaPluginConvention(ProjectInternal project, Instantiator instantiator) {
         this.project = project
-        def instantiator = project.services.get(Instantiator)
         sourceSets = instantiator.newInstance(DefaultSourceSetContainer.class, project.fileResolver, project.tasks, instantiator)
         dependencyCacheDirName = 'dependency-cache'
         docsDirName = 'docs'
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
deleted file mode 100644
index 1009a7d..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.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.api.plugins
-
-import org.gradle.api.Project
-import org.gradle.util.WrapUtil
-import org.gradle.api.reporting.ReportingExtension
-
-public class ProjectReportsPluginConvention {
-    /**
-     * The name of the directory to generate the project reports into, relative to the project's reports dir.
-     */
-    String projectReportDirName = 'project'
-    private final Project project
-
-    def ProjectReportsPluginConvention(Project project) {
-        this.project = project;
-    }
-
-    /**
-     * Returns the directory to generate the project reports into.
-     */
-    File getProjectReportDir() {
-        project.extensions.getByType(ReportingExtension).file(projectReportDirName)
-    }
-
-    Set<Project> getProjects() {
-        WrapUtil.toSet(project)
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index 6c8f550..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
+++ /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.api.plugins;
-
-import org.gradle.api.Plugin;
-import org.gradle.internal.reflect.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>
- *
- * <p>This plugin adds the following convention objects to the project:</p>
- *
- * <ul>
- *
- * <li>{@link ReportingBasePluginConvention}</li>
- *
- * </ul>
- */
-public class ReportingBasePlugin implements Plugin<ProjectInternal> {
-    public void apply(ProjectInternal project) {
-        Convention convention = project.getConvention();
-        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/github/GitHubDependenciesPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDependenciesPlugin.groovy
new file mode 100644
index 0000000..3ac4c3b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDependenciesPlugin.groovy
@@ -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.plugins.github
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory
+import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.plugins.github.internal.DefaultGitHubDownloadsRepository
+import org.gradle.internal.Factory
+import org.gradle.internal.reflect.Instantiator
+
+import javax.inject.Inject
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.Incubating
+
+ at Incubating
+class GitHubDependenciesPlugin implements Plugin<Project> {
+
+    FileResolver fileResolver
+    Instantiator instantiator
+    BaseRepositoryFactory resolverFactory
+
+    @Inject
+    GitHubDependenciesPlugin(FileResolver fileResolver, Instantiator instantiator, DependencyResolutionServices dependencyResolutionServices) {
+        this.fileResolver = fileResolver
+        this.instantiator = instantiator
+        this.resolverFactory = dependencyResolutionServices.baseRepositoryFactory
+    }
+
+    void apply(Project project) {
+        Factory<MavenArtifactRepository> mavenRepostoryFactory = new Factory<MavenArtifactRepository>() {
+            MavenArtifactRepository create() {
+                resolverFactory.createMavenRepository()
+            }
+        }
+
+        Factory<GitHubDownloadsRepository> downloadsRepositoryFactory = new Factory<GitHubDownloadsRepository>() {
+            GitHubDownloadsRepository create() {
+                def passwordCredentials = instantiator.newInstance(DefaultPasswordCredentials)
+                instantiator.newInstance(DefaultGitHubDownloadsRepository, fileResolver, passwordCredentials, mavenRepostoryFactory)
+            }
+        }
+
+        project.repositories.extensions.create("github", GitHubRepositoryHandlerExtension, project.repositories, downloadsRepositoryFactory)
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.java
new file mode 100644
index 0000000..c413ae1
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubDownloadsRepository.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.plugins.github;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.AuthenticationSupported;
+
+import java.net.URI;
+
+/**
+ * A dependency repository that uses GitHub downloads as the source.
+ *
+ * A value for {@link #setUser(String)} must be provided before this can be used.
+ * <p>
+ * Given the following repository definition:
+ * <pre>
+ * repositories {
+ *     github.downloads {
+ *       user = "githubUser"
+ *     }
+ * }</pre>
+ * <p>
+ * The following dependency notations will resolve to:
+ * <ul>
+ * <li>{@code myProject:myThing} - {@code https://github.com/downloads/githubUser/myProject/myThing.jar}
+ * <li>{@code myProject:myThing:1.0} - {@code https://github.com/downloads/githubUser/myProject/myThing-1.0.jar}
+ * <li>{@code myProject:myThing at zip} - {@code https://github.com/downloads/githubUser/myProject/myThing.zip}
+ * </ul>
+ */
+ at Incubating
+public interface GitHubDownloadsRepository extends ArtifactRepository, AuthenticationSupported {
+
+    /**
+     * {@value #DOWNLOADS_URL_BASE}
+     */
+    String DOWNLOADS_URL_BASE = "https://github.com/downloads";
+
+    /**
+     * Override the default base url of '{@value #DOWNLOADS_URL_BASE}'
+     *
+     * @param baseUrl The new base url
+     */
+    void setBaseUrl(Object baseUrl);
+
+    /**
+     * The base GitHub downloads url.
+     *
+     * Defaults to '{@value #DOWNLOADS_URL_BASE}'
+     *
+     * @return The base GitHub downloads url.
+     */
+    URI getBaseUrl();
+
+    /**
+     * Sets the GitHub user/organisation name that houses the downloads.
+     *
+     * Given a GitHub Downloads URL, this is the value at {@value #DOWNLOADS_URL_BASE}/«user».
+     *
+     * @param user The GitHub user/organisation name that houses the downloads.
+     */
+    void setUser(String user);
+
+    /**
+     * The GitHub user/organisation name that houses the downloads.
+     *
+     * @return The GitHub user/organisation name that houses the downloads.
+     */
+    String getUser();
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.java
new file mode 100644
index 0000000..af6dfed
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/GitHubRepositoryHandlerExtension.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.plugins.github;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.internal.Factory;
+
+/**
+ * Provides GitHub oriented dependency repository notations.
+ */
+ at Incubating
+public class GitHubRepositoryHandlerExtension {
+
+    private final RepositoryHandler repositories;
+    private final Factory<GitHubDownloadsRepository> downloadsRepositoryFactory;
+
+    public GitHubRepositoryHandlerExtension(RepositoryHandler repositories, Factory<GitHubDownloadsRepository> downloadsRepositoryFactory) {
+        this.repositories = repositories;
+        this.downloadsRepositoryFactory = downloadsRepositoryFactory;
+    }
+
+    public GitHubDownloadsRepository downloads(final String user) {
+        return downloads(new Action<GitHubDownloadsRepository>() {
+            public void execute(GitHubDownloadsRepository gitHubDownloadsRepository) {
+                gitHubDownloadsRepository.setUser(user);
+            }
+        });
+    }
+
+    public GitHubDownloadsRepository downloads(Action<GitHubDownloadsRepository> configure) {
+        GitHubDownloadsRepository repository = downloadsRepositoryFactory.create();
+        configure.execute(repository);
+        repositories.add(repository);
+        return repository;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java
new file mode 100644
index 0000000..40bb38f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/github/internal/DefaultGitHubDownloadsRepository.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.github.internal;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.plugins.github.GitHubDownloadsRepository;
+import org.gradle.internal.Factory;
+import org.gradle.util.ConfigureUtil;
+
+import java.net.URI;
+
+public class DefaultGitHubDownloadsRepository extends AbstractArtifactRepository implements GitHubDownloadsRepository, ArtifactRepositoryInternal {
+
+    private static final String PATTERN = "[organisation]/[artifact](-[revision])(-[classifier]).[ext]";
+
+    private String user;
+    private Object baseUrl = DOWNLOADS_URL_BASE;
+
+    private final PasswordCredentials credentials;
+    private final FileResolver fileResolver;
+    private final Factory<MavenArtifactRepository> repositoryFactory;
+
+    public DefaultGitHubDownloadsRepository(FileResolver fileResolver, PasswordCredentials credentials, Factory<MavenArtifactRepository> repositoryFactory) {
+        this.fileResolver = fileResolver;
+        this.credentials = credentials;
+        this.repositoryFactory = repositoryFactory;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public URI getBaseUrl() {
+        return fileResolver.resolveUri(baseUrl);
+    }
+
+    public void setBaseUrl(Object baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    public String getName() {
+        if (super.getName() == null) {
+            return getDefaultName();
+        } else {
+            return super.getName();
+        }
+    }
+
+    private String getDefaultName() {
+        String user = getUser();
+        return String.format("GitHub Downloads for GitHub user '%s'", user == null ? "" : user);
+    }
+
+
+    public PasswordCredentials getCredentials() {
+        return credentials;
+    }
+
+    public void credentials(Closure closure) {
+        ConfigureUtil.configure(closure, credentials);
+    }
+
+    public DependencyResolver createResolver() {
+        MavenArtifactRepository repository = repositoryFactory.create();
+        repository.setUrl(getEffectiveRepoUrl());
+        repository.setName(getName());
+        applyCredentialsTo(repository.getCredentials());
+
+        ArtifactRepositoryInternal repositoryInternal = toArtifactRepositoryInternal(repository);
+        MavenResolver resolver = toMavenResolver(repositoryInternal.createResolver());
+
+        resolver.setPattern(PATTERN);
+        return resolver;
+    }
+
+    private ArtifactRepositoryInternal toArtifactRepositoryInternal(ArtifactRepository artifactRepository) {
+        return (ArtifactRepositoryInternal) artifactRepository;
+    }
+
+    private MavenResolver toMavenResolver(DependencyResolver dependencyResolver) {
+        return (MavenResolver) dependencyResolver;
+    }
+
+    private URI getEffectiveRepoUrl() {
+        return fileResolver.resolveUri(String.format("%s/%s", getBaseUrl().toString(), getUser()));
+    }
+
+    private void applyCredentialsTo(PasswordCredentials other) {
+        other.setUsername(credentials.getUsername());
+        other.setPassword(credentials.getPassword());
+    }
+
+}
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
deleted file mode 100644
index 0a6a3f7..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/package-info.java
+++ /dev/null
@@ -1,20 +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.
- */
-
-/**
- * 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 9b578ce..be80099 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
@@ -179,6 +179,13 @@ public interface SourceSet {
     String getCompileTaskName(String language);
 
     /**
+     * Returns the name of the Jar task for this source set, if such a task exists.
+     *
+     * @return The task name. Never returns null.
+     */
+    String getJarTaskName();
+
+    /**
      * Returns the name of a task for this source set.
      *
      * @param verb The action, may be null.
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
index 47aced2..dff02e1 100644
--- 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
@@ -18,16 +18,14 @@ 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.internal.Factory;
 import org.gradle.internal.reflect.JavaReflectionUtil;
+import org.gradle.util.DeprecationLogger;
 
 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.
@@ -45,54 +43,49 @@ public abstract class AbstractOptions implements Serializable {
     }
 
     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.readProperty(this, field.getName());
-        if (value != null) {
-            map.put(antProperty(field.getName()), antValue(field.getName(), value));
-        }
+        final Class<?> thisClass = getClass();
+        return DeprecationLogger.whileDisabled(new Factory<Map<String, Object>>() {
+            public Map<String, Object> create() {
+                Map<String, Object> map = Maps.newHashMap();
+                Class<?> currClass = thisClass;
+                if (currClass.getName().endsWith("_Decorated")) {
+                    currClass = currClass.getSuperclass();
+                }
+                while (currClass != AbstractOptions.class) {
+                    for (Field field : currClass.getDeclaredFields()) {
+                        if (isOptionField(field)) {
+                            addValueToMapIfNotNull(map, field);
+                        }
+                    }
+                    currClass = currClass.getSuperclass();
+                }
+                return map;
+            }
+        });
     }
 
-    private boolean isOptionField(Field field) {
-        return ((field.getModifiers() & Modifier.STATIC) == 0)
-                && (!field.getName().equals("metaClass"))
-                && (!excludedFieldsFromOptionMap().contains(field.getName()));
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return false;
     }
 
-    private String antProperty(String fieldName) {
-        if (fieldName2AntMap().containsKey(fieldName)) {
-            return fieldName2AntMap().get(fieldName);
-        }
+    protected String getAntPropertyName(String 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);
-            }
-        }
+    protected Object getAntPropertyValue(String fieldName, Object value) {
         return value;
     }
 
-    protected List<String> excludedFieldsFromOptionMap() {
-        return Collections.emptyList();
-    }
-
-    protected Map<String, String> fieldName2AntMap() {
-        return Collections.emptyMap();
+    private void addValueToMapIfNotNull(Map<String, Object> map, Field field) {
+        Object value = JavaReflectionUtil.readProperty(this, field.getName());
+        if (value != null) {
+            map.put(getAntPropertyName(field.getName()), getAntPropertyValue(field.getName(), value));
+        }
     }
 
-    protected Map<String, ? extends Callable<Object>> fieldValue2AntMap() {
-        return Collections.emptyMap();
+    private boolean isOptionField(Field field) {
+        return ((field.getModifiers() & Modifier.STATIC) == 0)
+                && (!field.getName().equals("metaClass"))
+                && (!excludeFromAntProperties(field.getName()));
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/BaseForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/BaseForkOptions.java
new file mode 100644
index 0000000..2646237
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/BaseForkOptions.java
@@ -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.api.tasks.compile;
+
+import com.google.common.collect.Lists;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import java.util.List;
+
+/**
+ * Fork options for compilation. Only take effect if {@code fork}
+ * is {@code true}.
+ *
+ * @author Hans Dockter
+ */
+public class BaseForkOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    private String memoryInitialSize;
+
+    private String memoryMaximumSize;
+
+    private List<String> jvmArgs = Lists.newArrayList();
+
+    /**
+     * Returns the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
+    public String getMemoryInitialSize() {
+        return memoryInitialSize;
+    }
+
+    /**
+     * Sets the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
+    public void setMemoryInitialSize(String memoryInitialSize) {
+        this.memoryInitialSize = memoryInitialSize;
+    }
+
+    /**
+     * Returns the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
+    public String getMemoryMaximumSize() {
+        return memoryMaximumSize;
+    }
+
+    /**
+     * Sets the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
+    public void setMemoryMaximumSize(String memoryMaximumSize) {
+        this.memoryMaximumSize = memoryMaximumSize;
+    }
+
+    /**
+     * Returns any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
+     */
+    @Input
+    @Optional
+    public List<String> getJvmArgs() {
+        return jvmArgs;
+    }
+
+    /**
+     * Sets any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
+     */
+    public void setJvmArgs(List<String> jvmArgs) {
+        this.jvmArgs = jvmArgs;
+    }
+
+    @Override
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return fieldName.equals("jvmArgs");
+    }
+}
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 a56a101..1fa0a64 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
@@ -26,6 +26,7 @@ import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 
@@ -33,13 +34,18 @@ import java.io.File;
  * Compiles Java source files.
  *
  * @author Hans Dockter
+ * @deprecated This class has been replaced by {@link JavaCompile}.
  */
+ at Deprecated
 public class Compile extends AbstractCompile {
     private Compiler<JavaCompileSpec> javaCompiler;
     private File dependencyCacheDir;
-    private final JavaCompileSpec spec = new DefaultJavaCompileSpec();
+    private final CompileOptions compileOptions = new CompileOptions();
 
     public Compile() {
+        if (!(this instanceof JavaCompile)) {
+            DeprecationLogger.nagUserOfReplacedTaskType("Compile", "JavaCompile task type");
+        }
         Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
         JavaCompilerFactory inProcessCompilerFactory = new InProcessJavaCompilerFactory();
         ProjectInternal projectInternal = (ProjectInternal) getProject();
@@ -51,12 +57,14 @@ public class Compile extends AbstractCompile {
 
     @TaskAction
     protected void compile() {
+        DefaultJavaCompileSpec spec = new DefaultJavaCompileSpec();
         spec.setSource(getSource());
         spec.setDestinationDir(getDestinationDir());
         spec.setClasspath(getClasspath());
         spec.setDependencyCacheDir(getDependencyCacheDir());
         spec.setSourceCompatibility(getSourceCompatibility());
         spec.setTargetCompatibility(getTargetCompatibility());
+        spec.setCompileOptions(compileOptions);
         WorkResult result = javaCompiler.execute(spec);
         setDidWork(result.getDidWork());
     }
@@ -77,7 +85,7 @@ public class Compile extends AbstractCompile {
      */
     @Nested
     public CompileOptions getOptions() {
-        return spec.getCompileOptions();
+        return compileOptions;
     }
 
     public Compiler<JavaCompileSpec> getJavaCompiler() {
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
index 5ebd63e..3a41797 100644
--- 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
@@ -16,17 +16,15 @@
 
 package org.gradle.api.tasks.compile;
 
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 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.
@@ -36,6 +34,9 @@ import java.util.concurrent.Callable;
 public class CompileOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
+    private static final ImmutableSet<String> EXCLUDE_FROM_ANT_PROPERTIES =
+            ImmutableSet.of("debugOptions", "forkOptions", "compilerArgs", "dependOptions", "useDepend", "useAnt");
+
     private boolean failOnError = true;
 
     private boolean verbose;
@@ -174,7 +175,10 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Tells whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
      * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public boolean isOptimize() {
         return optimize;
     }
@@ -182,9 +186,12 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Tells whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
      * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
+     *
+     * @deprecated No replacement
      */
     // @Input not recognized if there is only an "is" method
     @Input
+    @Deprecated
     public boolean getOptimize() {
         return optimize;
     }
@@ -192,14 +199,18 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Sets whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
      * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setOptimize(boolean optimize) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.optimize", "There is no replacement for this property.");
         this.optimize = optimize;
     }
 
     /**
      * Tells whether to include debugging information in the generated class files. Defaults
-     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     * to {@code true}. See {@link DebugOptions#getDebugLevel()} for which debugging information will be generated.
      */
     public boolean isDebug() {
         return debug;
@@ -207,7 +218,7 @@ public class CompileOptions extends AbstractOptions {
 
     /**
      * Tells whether to include debugging information in the generated class files. Defaults
-     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     * to {@code true}. See {@link DebugOptions#getDebugLevel()} for which debugging information will be generated.
      */
     // @Input not recognized if there is only an "is" method
     @Input
@@ -217,7 +228,7 @@ public class CompileOptions extends AbstractOptions {
 
     /**
      * Sets whether to include debugging information in the generated class files. Defaults
-     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     * to {@code true}. See {@link DebugOptions#getDebugLevel()} for which debugging information will be generated.
      */
     public void setDebug(boolean debug) {
         this.debug = debug;
@@ -329,7 +340,10 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
      * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public boolean isIncludeJavaRuntime() {
         return includeJavaRuntime;
     }
@@ -337,9 +351,12 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
      * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
     // @Input not recognized if there is only an "is" method
     @Input
+    @Deprecated
     public boolean getIncludeJavaRuntime() {
         return includeJavaRuntime;
     }
@@ -347,8 +364,12 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Sets whether the Java runtime should be put on the compile class path. Only takes effect if
      * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.includeJavaRuntime", "There is no replacement for this property.");
         this.includeJavaRuntime = includeJavaRuntime;
     }
 
@@ -408,7 +429,10 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Tells whether to use the Ant javac task over Gradle's own Java compiler integration.
      * Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public boolean isUseAnt() {
         return useAnt;
     }
@@ -416,8 +440,12 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Sets whether to use the Ant javac task over Gradle's own Java compiler integration.
      * Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setUseAnt(boolean useAnt) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.useAnt", "There is no replacement for this property.");
         this.useAnt = useAnt;
     }
 
@@ -454,36 +482,35 @@ public class CompileOptions extends AbstractOptions {
     /**
      * Internal method.
      */
-    protected List<String> excludedFieldsFromOptionMap() {
-        return Arrays.asList("debugOptions", "forkOptions", "compilerArgs", "dependOptions", "useDepend", "useAnt");
+    public Map<String, Object> optionMap() {
+        Map<String, Object> map = super.optionMap();
+        map.putAll(debugOptions.optionMap());
+        map.putAll(forkOptions.optionMap());
+        return map;
     }
 
-    /**
-     * Internal method.
-     */
-    protected Map<String, String> fieldName2AntMap() {
-        return ImmutableMap.of("warnings", "nowarn", "bootClasspath", "bootclasspath", "extensionDirs", "extdirs", "failOnError", "failonerror", "listFiles", "listfiles");
+    @Override
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return EXCLUDE_FROM_ANT_PROPERTIES.contains(fieldName);
     }
 
-    /**
-     * Internal method.
-     */
-    protected Map<String, ? extends Callable<Object>> fieldValue2AntMap() {
-        return ImmutableMap.of("warnings", new Callable<Object>() {
-            public Object call() {
-                return !warnings;
-            }
-        });
+    @Override
+    protected String getAntPropertyName(String fieldName) {
+        if (fieldName.equals("warnings")) {
+            return "nowarn";
+        }
+        if (fieldName.equals("extensionDirs")) {
+            return "extdirs";
+        }
+        return fieldName;
     }
 
-    /**
-     * Internal method.
-     */
-    public Map<String, Object> optionMap() {
-        Map<String, Object> map = super.optionMap();
-        map.putAll(debugOptions.optionMap());
-        map.putAll(forkOptions.optionMap());
-        return map;
+    @Override
+    protected Object getAntPropertyValue(String fieldName, Object value) {
+        if (fieldName.equals("warnings")) {
+            return !warnings;
+        }
+        return value;
     }
 }
 
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
index 98c0dc0..be6b558 100644
--- 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
@@ -16,12 +16,9 @@
  
 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. Only take effect if {@link CompileOptions#debug}
  * is set to {@code true}.
@@ -60,11 +57,4 @@ public class DebugOptions extends AbstractOptions {
     public void setDebugLevel(String debugLevel) {
         this.debugLevel = debugLevel;
     }
-
-    /**
-     * Internal method.
-     */
-    protected Map<String, String> fieldName2AntMap() {
-        return ImmutableMap.of("debugLevel", "debuglevel");
-    }
 }
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
index 93bc1df..f87283b 100644
--- 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
@@ -15,9 +15,7 @@
  */
 package org.gradle.api.tasks.compile;
 
-import com.google.common.collect.ImmutableList;
-
-import java.util.List;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * Options for the Ant Depend task. Only take effect if {@code CompileOptions.useAnt} and
@@ -37,6 +35,8 @@ import java.util.List;
 public class DependOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
+    private static final ImmutableSet<String> EXCLUDE_FROM_ANT_PROPERTIES = ImmutableSet.of("srcDir", "destDir", "cache", "useCache");
+
     private boolean useCache = true;
 
     private boolean closure;
@@ -121,10 +121,8 @@ public class DependOptions extends AbstractOptions {
         this.warnOnRmiStubs = warnOnRmiStubs;
     }
 
-    /**
-     * Internal method.
-     */
-    protected List<String> excludedFieldsFromOptionMap() {
-        return ImmutableList.of("srcDir", "destDir", "cache", "useCache");
+    @Override
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return EXCLUDE_FROM_ANT_PROPERTIES.contains(fieldName);
     }
 }
\ No newline at end of file
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
index 70ec809..e3d7b1e 100644
--- 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
@@ -19,31 +19,18 @@ 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. Only take effect if {@code CompileOptions.fork} is {@code true}.
  *
  * @author Hans Dockter
  */
-public class ForkOptions extends AbstractOptions {
+public class ForkOptions extends BaseForkOptions {
     private static final long serialVersionUID = 0;
 
     private String executable;
 
-    private String memoryInitialSize;
-
-    private String memoryMaximumSize;
-
     private String tempDir;
 
-    private List<String> jvmArgs = Lists.newArrayList();
-
     /**
      * Returns the compiler executable to be used. If set,
      * a new compiler process will be forked for every compile task.
@@ -65,38 +52,6 @@ public class ForkOptions extends AbstractOptions {
     }
 
     /**
-     * Returns the initial heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public String getMemoryInitialSize() {
-        return memoryInitialSize;
-    }
-
-    /**
-     * Sets the initial heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public void setMemoryInitialSize(String memoryInitialSize) {
-        this.memoryInitialSize = memoryInitialSize;
-    }
-
-    /**
-     * Returns the maximum heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public String getMemoryMaximumSize() {
-        return memoryMaximumSize;
-    }
-
-    /**
-     * Sets the maximum heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public void setMemoryMaximumSize(String memoryMaximumSize) {
-        this.memoryMaximumSize = memoryMaximumSize;
-    }
-
-    /**
      * Returns the directory used for temporary files that may be created to pass
      * command line arguments to the compiler process. Defaults to {@code null},
      * in which case the directory will be chosen automatically.
@@ -114,35 +69,8 @@ public class ForkOptions extends AbstractOptions {
         this.tempDir = tempDir;
     }
 
-    /**
-     * Returns any additional JVM arguments for the compiler process.
-     * Defaults to the empty list.
-     */
-    @Input
-    @Optional
-    public List<String> getJvmArgs() {
-        return jvmArgs;
-    }
-
-    /**
-     * Sets any additional JVM arguments for the compiler process.
-     * Defaults to the empty list.
-     */
-    public void setJvmArgs(List<String> jvmArgs) {
-        this.jvmArgs = jvmArgs;
-    }
-
-    /**
-     * Internal method.
-     */
-    protected Map<String, String> fieldName2AntMap() {
-        return ImmutableMap.of("tempDir", "tempdir");
-    }
-
-    /**
-     * Internal method.
-     */
-    protected List<String> excludedFieldsFromOptionMap() {
-        return ImmutableList.of("jvmArgs");
+    @Override
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return fieldName.equals("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 4960631..e2aeca4 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
@@ -43,7 +43,8 @@ import java.util.List;
 public class GroovyCompile extends AbstractCompile {
     private Compiler<GroovyJavaJointCompileSpec> compiler;
     private FileCollection groovyClasspath;
-    private final GroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec();
+    private final CompileOptions compileOptions = new CompileOptions();
+    private final GroovyCompileOptions groovyCompileOptions = new GroovyCompileOptions();
     private final TemporaryFileProvider tempFileProvider;
     
     public GroovyCompile() {
@@ -54,7 +55,6 @@ public class GroovyCompile extends AbstractCompile {
         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());
@@ -63,12 +63,15 @@ public class GroovyCompile extends AbstractCompile {
     protected void compile() {
         List<File> taskClasspath = new ArrayList<File>(getGroovyClasspath().getFiles());
         throwExceptionIfTaskClasspathIsEmpty(taskClasspath);
+        DefaultGroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec();
         spec.setSource(getSource());
         spec.setDestinationDir(getDestinationDir());
         spec.setClasspath(getClasspath());
         spec.setSourceCompatibility(getSourceCompatibility());
         spec.setTargetCompatibility(getTargetCompatibility());
         spec.setGroovyClasspath(taskClasspath);
+        spec.setCompileOptions(compileOptions);
+        spec.setGroovyCompileOptions(groovyCompileOptions);
         if (spec.getGroovyCompileOptions().getStubDir() == null) {
             File dir = tempFileProvider.newTemporaryFile("groovy-java-stubs");
             dir.mkdirs();
@@ -92,7 +95,7 @@ public class GroovyCompile extends AbstractCompile {
      */
     @Nested
     public GroovyCompileOptions getGroovyOptions() {
-        return spec.getGroovyCompileOptions();
+        return groovyCompileOptions;
     }
 
     /**
@@ -102,7 +105,7 @@ public class GroovyCompile extends AbstractCompile {
      */
     @Nested
     public CompileOptions getOptions() {
-        return spec.getCompileOptions();
+        return compileOptions;
     }
 
     /**
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
index 52db233..26812f8 100644
--- 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
@@ -16,11 +16,12 @@
 package org.gradle.api.tasks.compile;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 import org.gradle.api.tasks.Input;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 import java.util.List;
@@ -33,6 +34,8 @@ import java.util.Map;
  */
 public class GroovyCompileOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
+    private static final ImmutableSet<String> EXCLUDE_FROM_ANT_PROPERTIES =
+            ImmutableSet.of("forkOptions", "optimizationOptions", "useAnt", "stubDir", "keepStubs", "fileExtensions");
 
     private boolean failOnError = true;
 
@@ -176,51 +179,75 @@ public class GroovyCompileOptions extends AbstractOptions {
 
     /**
      * Tells whether to print a stack trace when the compiler hits a problem (like a compile error).
-     * Defaults to {@code false}.
+     * Defaults to {@code false}. Only used when {@link #isUseAnt()} is {@code true}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public boolean isStacktrace() {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.stacktrace", "There is no replacement for this property.");
         return stacktrace;
     }
 
     /**
      * Sets whether to print a stack trace when the compiler hits a problem (like a compile error).
-     * Defaults to {@code false}.
+     * Defaults to {@code false}. Only used when {@link #isUseAnt()} is {@code true}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setStacktrace(boolean stacktrace) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.stacktrack", "This property has no replacement.");
         this.stacktrace = stacktrace;
     }
 
     /**
      * Tells whether the groovyc Ant task should be used over Gradle's own Groovy compiler integration.
      * Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
     @Input
+    @Deprecated
     public boolean isUseAnt() {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.useAnt", "There is no replacement for this property.");
         return useAnt;
     }
 
     /**
      * Sets whether the groovyc Ant task should be used over Gradle's own Groovy compiler integration.
      * Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setUseAnt(boolean useAnt) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.useAnt", "There is no replacement for this property.");
         this.useAnt = useAnt;
     }
 
     /**
      * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
      * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
     @Input
+    @Deprecated
     public boolean isIncludeJavaRuntime() {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.includeJavaRuntime", "There is no replacement for this property.");
         return includeJavaRuntime;
     }
 
     /**
      * Sets whether the Java runtime should be put on the compile class path. Only takes effect if
      * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     *
+     * @deprecated No replacement
      */
+    @Deprecated
     public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("GroovyCompileOptions.includeJavaRuntime", "There is no replacement for this property.");
         this.includeJavaRuntime = includeJavaRuntime;
     }
 
@@ -245,7 +272,7 @@ public class GroovyCompileOptions extends AbstractOptions {
      * Groovy 1.7 or higher. Defaults to {@code ImmutableList.of("java", "groovy")}.
      */
     @Input
-    @Experimental
+    @Incubating
     public List<String> getFileExtensions() {
         return fileExtensions;
     }
@@ -254,7 +281,7 @@ public class GroovyCompileOptions extends AbstractOptions {
      * Sets the list of acceptable source file extensions. Only takes effect when compiling against
      * Groovy 1.7 or higher. Defaults to {@code ImmutableList.of("java", "groovy")}.
      */
-    @Experimental
+    @Incubating
     public void setFileExtensions(List<String> fileExtensions) {
         this.fileExtensions = fileExtensions;
     }
@@ -278,7 +305,7 @@ public class GroovyCompileOptions extends AbstractOptions {
     }
 
     /**
-     * Convenience method to set {@link ForkOptions} with named parameter syntax.
+     * Convenience method to set {@link GroovyForkOptions} with named parameter syntax.
      * Calling this method will set {@code fork} to {@code true}.
      */
     public GroovyCompileOptions fork(Map forkArgs) {
@@ -287,18 +314,9 @@ public class GroovyCompileOptions extends AbstractOptions {
         return this;
     }
 
-    /**
-     * Internal method.
-     */
-    protected List<String> excludedFieldsFromOptionMap() {
-        return ImmutableList.of("forkOptions", "optimizationOptions", "useAnt", "stubDir", "keepStubs", "fileExtensions");
-    }
-
-    /**
-     * Internal method.
-     */
-    protected Map<String, String> fieldName2AntMap() {
-        return ImmutableMap.of("failOnError", "failonerror", "listFiles", "listfiles");
+    @Override
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return EXCLUDE_FROM_ANT_PROPERTIES.contains(fieldName);
     }
 
     /**
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
index f5b78e5..5aecce1 100644
--- 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
@@ -15,82 +15,12 @@
  */
 package org.gradle.api.tasks.compile;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-
-import java.util.List;
-
 /**
  * Fork options for Groovy compilation. Only take effect if {@code GroovyCompileOptions.fork}
  * is {@code true}.
  *
  * @author Hans Dockter
  */
-public class GroovyForkOptions extends AbstractOptions {
+public class GroovyForkOptions extends BaseForkOptions {
     private static final long serialVersionUID = 0;
-
-    private String memoryInitialSize;
-
-    private String memoryMaximumSize;
-
-    private List<String> jvmArgs = Lists.newArrayList();
-
-    /**
-     * Returns the initial heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public String getMemoryInitialSize() {
-        return memoryInitialSize;
-    }
-
-    /**
-     * Sets the initial heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public void setMemoryInitialSize(String memoryInitialSize) {
-        this.memoryInitialSize = memoryInitialSize;
-    }
-
-    /**
-     * Returns the maximum heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public String getMemoryMaximumSize() {
-        return memoryMaximumSize;
-    }
-
-    /**
-     * Sets the maximum heap size for the compiler process.
-     * Defaults to {@code null}, in which case the JVM's default will be used.
-     */
-    public void setMemoryMaximumSize(String memoryMaximumSize) {
-        this.memoryMaximumSize = memoryMaximumSize;
-    }
-
-    /**
-     * Returns any additional JVM arguments for the compiler process.
-     * Defaults to the empty list.
-     */
-    @Input
-    @Optional
-    public List<String> getJvmArgs() {
-        return jvmArgs;
-    }
-
-    /**
-     * Sets any additional JVM arguments for the compiler process.
-     * Defaults to the empty list.
-     */
-    public void setJvmArgs(List<String> jvmArgs) {
-        this.jvmArgs = jvmArgs;
-    }
-
-    /**
-     * Internal method.
-     */
-    protected List<String> excludedFieldsFromOptionMap() {
-        return ImmutableList.of("jvmArgs");
-    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/JavaCompile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/JavaCompile.java
new file mode 100644
index 0000000..4cff198
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/JavaCompile.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.tasks.compile;
+
+/**
+ * Compiles Java source files.
+ */
+public class JavaCompile extends Compile {
+}
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 9e60699..4b414db 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
@@ -106,7 +106,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     private boolean ignoreFailures;
     private FileCollection classpath;
     private TestFramework testFramework;
-    private boolean testReport = true;
+    private boolean testReport;
     private boolean scanForTestClasses = true;
     private long forkEvery;
     private int maxParallelForks = 1;
@@ -733,17 +733,20 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * <p>Returns the test options options.</p>
+     * Returns test framework specific options. Make sure to call {@link #useJUnit()} or {@link #useTestNG()} before using this method.
      *
-     * <p>Be sure to call the appropriate {@link #useJUnit()} or {@link #useTestNG()} method before using this method.</p>
-     *
-     * @return The testframework options.
+     * @return The test framework options.
      */
     @Nested
     public TestFrameworkOptions getOptions() {
         return options(null);
     }
 
+    /**
+     * Configures test framework specific options. Make sure to call {@link #useJUnit()} or {@link #useTestNG()} before using this method.
+     *
+     * @return The test framework options.
+     */
     public TestFrameworkOptions options(Closure testFrameworkConfigure) {
         TestFrameworkOptions options = getTestFramework().getOptions();
         ConfigureUtil.configure(testFrameworkConfigure, testFramework.getOptions());
@@ -816,6 +819,21 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
 
     /**
      * Specifies whether the test report should be generated.
+     * <p>
+     * Since Gradle 1.3 the TestNG uses this property in the following way:
+     * <ul>
+     *     <li>report 'on' means that new TestNG reporting is used:
+     *     new improved html reports are generated, old reports are not generated,
+     *     xml junit results (typically consumed by your CI server) are generated to {@link Test#getTestResultsDir()} dir
+     *     (previously those were generated into {@code Test.testReportsDir/junitreports} dir).
+     *     </li>
+     *     <li>report 'off' means that old TestNG reporting is used: old html reports are generated,
+     *     the xml junit results (for CI) are generated to {@code Test.testReportsDir/junitreports} dir.
+     *     </li>
+     * </ul>
+     * Even though the property is processed differently by TestNG tests, the default behavior does not change.
+     * The changes above were needed so that we can improve the TestNG reporting in a backwards compatible way.
+     * For more information please refer to the release notes for Gradle 1.3.
      */
     @Input
     public boolean isTestReport() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java
index 7323dbf..f703cdc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java
@@ -16,12 +16,9 @@
 
 package org.gradle.api.tasks.testing.logging;
 
-import org.gradle.api.Experimental;
-
 /**
  * Determines how exceptions are formatted in test logging.
  */
- at Experimental
 public enum TestExceptionFormat {
     /**
      * Short display of exceptions.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java
index 013b61d..f090a49 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java
@@ -16,12 +16,9 @@
 
 package org.gradle.api.tasks.testing.logging;
 
-import org.gradle.api.Experimental;
-
 /**
  * Test events that can be logged.
  */
- at Experimental
 public enum TestLogEvent {
     /**
      * A test has started. This event gets fired both for atomic and composite tests.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java
index a52eb5e..a3a82c9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java
@@ -16,8 +16,6 @@
 
 package org.gradle.api.tasks.testing.logging;
 
-import org.gradle.api.Experimental;
-
 import java.util.Set;
 
 /**
@@ -29,7 +27,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @return the events to be logged
      */
-    @Experimental
     Set<TestLogEvent> getEvents();
 
     /**
@@ -37,99 +34,73 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @param events the events to be logged
      */
-    @Experimental
     void setEvents(Iterable<?> events);
 
     /**
-     * Sets the events to be logged. Events can be passed as enum values
-     * (e.g. {@link TestLogEvent#FAILED}) or Strings (e.g. "failed").
+     * Sets the events to be logged. Events can be passed as enum values (e.g. {@link TestLogEvent#FAILED}) or Strings (e.g. "failed").
      *
      * @param events the events to be logged
      */
-    @Experimental
     void events(Object... events);
 
     /**
-     * Returns the minimum granularity of the events to be logged. Typically, 0
-     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
-     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
-     * and 3 corresponds to a test method. These values will vary if user-defined
-     * suites are executed.
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Returns the minimum granularity of the events to be logged. Typically, 0 corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to the Gradle-generated test suite
+     * for a particular test JVM, 2 corresponds to a test class, and 3 corresponds to a test method. These values will vary if user-defined suites are executed. <p>-1 denotes the highest granularity
+     * and corresponds to an atomic test.
      *
      * @return the minimum granularity of the events to be logged
      */
-    @Experimental
     int getMinGranularity();
 
     /**
-     * Sets the minimum granularity of the events to be logged. Typically, 0
-     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
-     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
-     * and 3 corresponds to a test method. These values will vary if user-defined
-     * suites are executed.
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Sets the minimum granularity of the events to be logged. Typically, 0 corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to the Gradle-generated test suite for
+     * a particular test JVM, 2 corresponds to a test class, and 3 corresponds to a test method. These values will vary if user-defined suites are executed. <p>-1 denotes the highest granularity and
+     * corresponds to an atomic test.
      *
      * @param granularity the minimum granularity of the events to be logged
      */
-    @Experimental
     void setMinGranularity(int granularity);
 
     /**
-     * Returns the maximum granularity of the events to be logged. Typically, 0
-     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
-     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
-     * and 3 corresponds to a test method. These values will vary if user-defined
-     * suites are executed.
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Returns the maximum granularity of the events to be logged. Typically, 0 corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to the Gradle-generated test suite
+     * for a particular test JVM, 2 corresponds to a test class, and 3 corresponds to a test method. These values will vary if user-defined suites are executed. <p>-1 denotes the highest granularity
+     * and corresponds to an atomic test.
      *
      * @return the maximum granularity of the events to be logged
      */
-    @Experimental
     int getMaxGranularity();
 
     /**
-     * Returns the maximum granularity of the events to be logged. Typically, 0
-     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
-     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
-     * and 3 corresponds to a test method. These values will vary if user-defined
-     * suites are executed.
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Returns the maximum granularity of the events to be logged. Typically, 0 corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to the Gradle-generated test suite
+     * for a particular test JVM, 2 corresponds to a test class, and 3 corresponds to a test method. These values will vary if user-defined suites are executed. <p>-1 denotes the highest granularity
+     * and corresponds to an atomic test.
      *
      * @param granularity the maximum granularity of the events to be logged
      */
-    @Experimental
     void setMaxGranularity(int granularity);
 
     /**
-     * Returns the display granularity of the events to be logged. For example, if set to 0,
-     * a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod".
-     * If set to 2, the same event will be displayed as "org.someClass > org.someMethod".
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Returns the display granularity of the events to be logged. For example, if set to 0, a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod". If
+     * set to 2, the same event will be displayed as "org.someClass > org.someMethod". <p>-1 denotes the highest granularity and corresponds to an atomic test.
      *
      * @return the display granularity of the events to be logged
      */
-    @Experimental
     int getDisplayGranularity();
 
     /**
-     * Sets the display granularity of the events to be logged. For example, if set to 0,
-     * a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod".
-     * If set to 2, the same event will be displayed as "org.someClass > org.someMethod".
-     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     * Sets the display granularity of the events to be logged. For example, if set to 0, a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod". If set
+     * to 2, the same event will be displayed as "org.someClass > org.someMethod". <p>-1 denotes the highest granularity and corresponds to an atomic test.
      *
      * @param granularity the display granularity of the events to be logged
      */
-    @Experimental
+
     void setDisplayGranularity(int granularity);
 
     /**
-     * Tells whether exceptions that occur during test execution will be logged.
-     * Typically these exceptions coincide with a "failed" event.
+     * Tells whether exceptions that occur during test execution will be logged. Typically these exceptions coincide with a "failed" event.
      *
      * @return whether exceptions that occur during test execution will be logged
      */
-    @Experimental
     boolean getShowExceptions();
 
     /**
@@ -137,25 +108,20 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @param flag whether exceptions that occur during test execution will be logged
      */
-    @Experimental
     void setShowExceptions(boolean flag);
 
     /**
-     * Tells whether causes of exceptions that occur during test execution will be logged.
-     * Only relevant if {@code showExceptions} is {@code true}.
+     * Tells whether causes of exceptions that occur during test execution will be logged. Only relevant if {@code showExceptions} is {@code true}.
      *
      * @return whether causes of exceptions that occur during test execution will be logged
      */
-    @Experimental
     boolean getShowCauses();
 
     /**
-     * Sets whether causes of exceptions that occur during test execution will be logged.
-     * Only relevant if {@code showExceptions} is {@code true}.
+     * Sets whether causes of exceptions that occur during test execution will be logged. Only relevant if {@code showExceptions} is {@code true}.
      *
      * @param flag whether causes of exceptions that occur during test execution will be logged
      */
-    @Experimental
     void setShowCauses(boolean flag);
 
     /**
@@ -163,7 +129,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @return whether stack traces of exceptions that occur during test execution will be logged
      */
-    @Experimental
     boolean getShowStackTraces();
 
     /**
@@ -171,7 +136,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @param flag whether stack traces of exceptions that occur during test execution will be logged
      */
-    @Experimental
     void setShowStackTraces(boolean flag);
 
     /**
@@ -179,7 +143,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @return the format to be used for logging test exceptions
      */
-    @Experimental
     TestExceptionFormat getExceptionFormat();
 
     /**
@@ -187,7 +150,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @param exceptionFormat the format to be used for logging test exceptions
      */
-    @Experimental
     void setExceptionFormat(Object exceptionFormat);
 
     /**
@@ -195,7 +157,6 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @return the set of filters to be used for sanitizing test stack traces
      */
-    @Experimental
     Set<TestStackTraceFilter> getStackTraceFilters();
 
     /**
@@ -203,24 +164,21 @@ public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
      *
      * @param stackTraces the set of filters to be used for sanitizing test stack traces
      */
-    @Experimental
     void setStackTraceFilters(Iterable<?> stackTraces);
 
     /**
      * Convenience method for {@link #setStackTraceFilters(java.lang.Iterable)}. Accepts both enum values and Strings.
      */
-    @Experimental
     void stackTraceFilters(Object... stackTraces);
 
     /**
-     * Tells whether output on standard out and standard error will be logged. Equivalent to checking if both
-     * log events {@link TestLogEvent#STANDARD_OUT} and {@link TestLogEvent#STANDARD_ERROR} are set.
+     * Tells whether output on standard out and standard error will be logged. Equivalent to checking if both log events {@code TestLogEvent.STANDARD_OUT} and {@code TestLogEvent.STANDARD_ERROR} are
+     * set.
      */
-     boolean getShowStandardStreams();
+    boolean getShowStandardStreams();
 
     /**
-     * Sets whether output on standard out and standard error will be logged. Equivalent to setting
-     * log events {@link TestLogEvent#STANDARD_OUT} and {@link TestLogEvent#STANDARD_ERROR}.
+     * Sets whether output on standard out and standard error will be logged. Equivalent to setting log events {@code TestLogEvent.STANDARD_OUT} and {@code TestLogEvent.STANDARD_ERROR}.
      */
-     TestLogging setShowStandardStreams(boolean flag);
+    TestLogging setShowStandardStreams(boolean flag);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java
index 66a5921..ca2fed2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java
@@ -17,7 +17,6 @@
 package org.gradle.api.tasks.testing.logging;
 
 import org.gradle.api.Action;
-import org.gradle.api.Experimental;
 import org.gradle.api.logging.LogLevel;
 
 /**
@@ -45,7 +44,6 @@ import org.gradle.api.logging.LogLevel;
  * The defaults that are in place show progressively more information
  * on log levels LIFECYCLE, INFO, and DEBUG, respectively.
  */
- at Experimental
 public interface TestLoggingContainer extends TestLogging {
     /**
      * Returns logging options for debug level.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java
index 743fe8d..cc752c1 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java
@@ -16,12 +16,9 @@
 
 package org.gradle.api.tasks.testing.logging;
 
-import org.gradle.api.Experimental;
-
 /**
  * Stack trace filters for test logging. Multiple filters can be combined.
  */
- at Experimental
 public enum TestStackTraceFilter {
     // ordered by how much they filter
     ENTRY_POINT, TRUNCATE, GROOVY
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java
index c6778f7..6d3b759 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java
@@ -17,7 +17,5 @@
 /**
  * Types related to logging of test related information to the console.
  */
- at Experimental
 package org.gradle.api.tasks.testing.logging;
 
-import org.gradle.api.Experimental;
\ No newline at end of file
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 48ce138..12a513f 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
@@ -125,7 +125,7 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     /**
      * Add suite files by Strings. Each suiteFile String should be a path relative to the project root.
      */
-    void suites(String ... suiteFiles) {
+    void suites(String... suiteFiles) {
         suiteFiles.each {
             suiteXmlFiles.add(new File(projectDir, it))
         }
@@ -134,7 +134,7 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     /**
      * Add suite files by File objects.
      */
-    void suites(File ... suiteFiles) {
+    void suites(File... suiteFiles) {
         suiteXmlFiles.addAll(Arrays.asList(suiteFiles))
     }
 
@@ -171,12 +171,12 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
         this
     }
 
-    TestNGOptions includeGroups(String ... includeGroups) {
+    TestNGOptions includeGroups(String... includeGroups) {
         this.includeGroups.addAll(Arrays.asList(includeGroups))
         this
     }
 
-    TestNGOptions excludeGroups(String ... excludeGroups) {
+    TestNGOptions excludeGroups(String... excludeGroups) {
         this.excludeGroups.addAll(Arrays.asList(excludeGroups))
         this
     }
@@ -194,16 +194,14 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     Object propertyMissing(String name) {
         if (suiteXmlBuilder != null) {
             return suiteXmlBuilder.getMetaClass()."${name}"
-        } else {
-            return super.propertyMissing(name)
         }
+        throw new MissingPropertyException(name, getClass());
     }
 
     Object methodMissing(String name, Object args) {
         if (suiteXmlBuilder != null) {
             return suiteXmlBuilder.getMetaClass().invokeMethod(suiteXmlBuilder, name, args)
-        } else {
-            return super.methodMissing(name, args)
         }
+        throw new MissingMethodException(name, getClass(), args)
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOption.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOption.java
index 2077351..27f8d9c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOption.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/GroupsJavadocOptionFileOption.java
@@ -16,7 +16,7 @@
 
 package org.gradle.external.javadoc.internal;
 
-import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 
 import java.io.IOException;
 import java.util.LinkedHashMap;
@@ -51,7 +51,7 @@ public class GroupsJavadocOptionFileOption extends AbstractJavadocOptionFileOpti
                     .write(
                         new StringBuffer()
                             .append("\"")
-                            .append(GUtil.join(groupPackages, ":"))
+                            .append(CollectionUtils.join(":", groupPackages))
                             .append("\"")
                             .toString())
                     .newLine();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
index 7570dbd..167af9f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
@@ -16,14 +16,15 @@
 
 package org.gradle.external.javadoc.internal;
 
-import org.apache.commons.io.IOUtils;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.IoActions;
 import org.gradle.external.javadoc.JavadocOptionFileOption;
 
-import java.io.IOException;
 import java.io.BufferedWriter;
-import java.io.FileWriter;
 import java.io.File;
+import java.io.IOException;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * @author Tom Eyckmans
@@ -39,19 +40,23 @@ public class JavadocOptionFileWriter {
     }
 
     void write(File outputFile) throws IOException {
-        BufferedWriter writer = null;
-        try {
-            final Map<String, JavadocOptionFileOption> options = optionFile.getOptions();
-            writer = new BufferedWriter(new FileWriter(outputFile));
-            JavadocOptionFileWriterContext writerContext = new JavadocOptionFileWriterContext(writer);
-
-            for (final String option : options.keySet()) {
-                options.get(option).write(writerContext);
+        IoActions.writeFile(outputFile, new ErroringAction<BufferedWriter>() {
+            @Override
+            protected void doExecute(BufferedWriter writer) throws Exception {
+                final Map<String, JavadocOptionFileOption> options = new TreeMap<String, JavadocOptionFileOption>(optionFile.getOptions());
+                JavadocOptionFileWriterContext writerContext = new JavadocOptionFileWriterContext(writer);
+
+                JavadocOptionFileOption localeOption = options.remove("locale");
+                if (localeOption != null) {
+                    localeOption.write(writerContext);
+                }
+
+                for (final String option : options.keySet()) {
+                    options.get(option).write(writerContext);
+                }
+
+                optionFile.getSourceNames().write(writerContext);
             }
-
-            optionFile.getSourceNames().write(writerContext);
-        } finally {
-            IOUtils.closeQuietly(writer);
-        }
+        });
     }
 }
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/github-dependencies.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/github-dependencies.properties
new file mode 100644
index 0000000..6846864
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/github-dependencies.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.github.GitHubDependenciesPlugin
\ No newline at end of file
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
index 5fb166b..8732f7f 100644
--- 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
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 ##############################################################################
 ##
@@ -61,9 +61,9 @@ while [ -h "\$PRG" ] ; do
     fi
 done
 SAVED="`pwd`"
-cd "`dirname \"\$PRG\"`/${appHomeRelativePath}"
+cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >&-
 APP_HOME="`pwd -P`"
-cd "\$SAVED"
+cd "\$SAVED" >&-
 
 CLASSPATH=$classpath
 
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 c623e6f..b00bc1e 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
@@ -94,9 +94,9 @@ class DefaultSourceSetTest {
         assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileSetNameJava'))
         assertThat(sourceSet.compileJavaTaskName, equalTo('compileSetNameJava'))
         assertThat(sourceSet.processResourcesTaskName, equalTo('processSetNameResources'))
+        assertThat(sourceSet.jarTaskName, equalTo('setNameJar'))
         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"))
     }
@@ -108,6 +108,7 @@ class DefaultSourceSetTest {
         assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileJava'))
         assertThat(sourceSet.compileJavaTaskName, equalTo('compileJava'))
         assertThat(sourceSet.processResourcesTaskName, equalTo('processResources'))
+        assertThat(sourceSet.jarTaskName, equalTo('jar'))
         assertThat(sourceSet.getTaskName('build', null), equalTo('buildMain'))
         assertThat(sourceSet.getTaskName(null, 'jar'), equalTo('jar'))
         assertThat(sourceSet.getTaskName('build', 'jar'), equalTo('buildJar'))
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
index 071a224..1e1dcf7 100644
--- 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
@@ -16,6 +16,7 @@
 
 package org.gradle.api.internal.tasks.compile
 
+import org.gradle.api.tasks.compile.CompileOptions
 import org.gradle.util.TemporaryFolder
 import org.gradle.api.internal.file.TemporaryFileProvider
 import org.gradle.api.internal.file.collections.SimpleFileCollection
@@ -59,6 +60,7 @@ class CommandLineJavaCompilerArgumentsGeneratorTest extends Specification {
         def sources = createFiles(numFiles)
         def classpath = createFiles(numFiles)
         def spec = new DefaultJavaCompileSpec()
+        spec.compileOptions = new CompileOptions()
         spec.compileOptions.forkOptions.memoryMaximumSize = "256m"
         spec.source = new SimpleFileCollection(sources)
         spec.classpath = new SimpleFileCollection(classpath)
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
index c6f8fd8..24d2348 100644
--- 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
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.tasks.compile
 
+import org.gradle.api.tasks.compile.CompileOptions
 import spock.lang.Specification
 import org.gradle.api.JavaVersion
 import org.gradle.api.internal.file.collections.SimpleFileCollection
@@ -23,6 +24,10 @@ class JavaCompilerArgumentsBuilderTest extends Specification {
     def spec = new DefaultJavaCompileSpec()
     def builder = new JavaCompilerArgumentsBuilder(spec)
 
+    def setup() {
+        spec.compileOptions = new CompileOptions()
+    }
+
     def "generates options for an unconfigured spec"() {
         expect:
         builder.build() == ["-g"]
@@ -186,6 +191,33 @@ class JavaCompilerArgumentsBuilderTest extends Specification {
         builder.build() == ["-source", "1.4", "-g"]
     }
 
+    def "can include/exclude classpath"() {
+        def file1 = new File("/lib/lib1.jar")
+        def file2 = new File("/lib/lib2.jar")
+        spec.classpath = [file1, file2]
+
+        when:
+        builder.includeClasspath(true)
+
+        then:
+        builder.build() == ["-g", "-classpath", "$file1$File.pathSeparator$file2"]
+
+        when:
+        builder.includeClasspath(false)
+
+        then:
+        builder.build() == ["-g"]
+    }
+
+    def "includes classpath by default"() {
+        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 "can include/exclude launcher options"() {
         spec.compileOptions.forkOptions.with {
             memoryInitialSize = "64m"
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy
index 4e5ae65..4c42148 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy
@@ -18,18 +18,21 @@ package org.gradle.api.internal.tasks.compile
 import org.gradle.api.internal.file.collections.SimpleFileCollection
 
 import groovy.transform.InheritConstructors
-
+import org.gradle.api.tasks.compile.CompileOptions
+import org.gradle.api.tasks.compile.GroovyCompileOptions
 import spock.lang.Specification
 
 class NormalizingGroovyCompilerTest extends Specification { 
     Compiler<GroovyJavaJointCompileSpec> target = Mock()
-    GroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec()
+    DefaultGroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec()
     NormalizingGroovyCompiler compiler = new NormalizingGroovyCompiler(target)
     
     def setup() {
         spec.classpath = files('Dep1.jar', 'Dep2.jar', 'Dep3.jar')
         spec.groovyClasspath = spec.classpath
         spec.source = files('House.scala', 'Person1.java', 'package.html', 'Person2.groovy')
+        spec.compileOptions = new CompileOptions()
+        spec.groovyCompileOptions = new GroovyCompileOptions()
     }
 
     def "silently excludes source files not ending in .java or .groovy by default"() {
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
index 121a54b..6a33dd6 100644
--- 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
@@ -19,17 +19,18 @@ import org.gradle.api.tasks.WorkResult
 import org.gradle.api.internal.file.collections.SimpleFileCollection
 
 import groovy.transform.InheritConstructors
-
+import org.gradle.api.tasks.compile.CompileOptions
 import spock.lang.Specification
 
 class NormalizingJavaCompilerTest extends Specification {
     Compiler<JavaCompileSpec> target = Mock()
-    JavaCompileSpec spec = new DefaultJavaCompileSpec()
+    DefaultJavaCompileSpec 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")
+        spec.compileOptions = new CompileOptions()
     }
 
     def "delegates to target compiler after resolving source and classpath"() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.groovy
new file mode 100644
index 0000000..51ce83a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuterTest.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.api.internal.tasks.testing.detection
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.tasks.testing.TestFramework
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.tasks.testing.Test
+import org.gradle.messaging.actor.Actor
+import org.gradle.messaging.actor.ActorFactory
+import spock.lang.Specification
+
+class DefaultTestExecuterTest extends Specification {
+
+    TestResultProcessor testResultProcessor = Mock()
+    Test testTask = Mock()
+    ActorFactory actorFactory = Mock()
+    org.gradle.internal.Factory workerFactory = Mock()
+    TestFramework testFramework = Mock()
+    TestResultProcessor resultProcessor = Mock()
+    Actor resultProcessorActor = Mock()
+    TestFrameworkDetector testFrameworkTestDetector = Mock()
+    File testClassesDir = Mock()
+    FileCollection testClasspath = Mock()
+
+    DefaultTestExecuter executer = new DefaultTestExecuter(workerFactory, actorFactory)
+
+    def setup() {
+        _ * testTask.testFramework >> testFramework
+        _ * testTask.getCandidateClassFiles() >> Mock(FileTree)
+        _ * actorFactory.createActor(_) >> resultProcessorActor
+        _ * resultProcessorActor.getProxy(_) >> resultProcessor
+        _ * testTask.isScanForTestClasses() >> true
+        _ * testFramework.getDetector() >> testFrameworkTestDetector
+    }
+
+    def "testClassDirectory for testclassdetector is configured before executing"() {
+        when:
+        executer.execute(testTask, testResultProcessor);
+        then:
+        1 * testFramework.getDetector() >> testFrameworkTestDetector
+        1 * testTask.getTestClassesDir() >> testClassesDir
+        1 * testFrameworkTestDetector.setTestClassesDirectory(testClassesDir);
+    }
+
+    def "testClasspath for testclassdetector is configured before executing"() {
+        when:
+        executer.execute(testTask, testResultProcessor);
+        then:
+        1 * testTask.getClasspath() >> testClasspath
+        1 * testFrameworkTestDetector.setTestClasspath(testClasspath)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
index 5c95aed..0af2f40 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
@@ -17,39 +17,33 @@
 package org.gradle.api.internal.tasks.testing.testng
 
 import org.gradle.api.GradleException
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
-import org.gradle.api.internal.tasks.testing.TestResultProcessor
-
 import org.gradle.api.tasks.testing.TestResult.ResultType
 import org.gradle.api.tasks.testing.testng.TestNGOptions
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.internal.id.LongIdGenerator
+import org.gradle.logging.StandardOutputRedirector
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TemporaryFolder
 import org.jmock.Sequence
 import org.jmock.integration.junit4.JMock
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.testng.annotations.AfterClass
-import org.testng.annotations.BeforeClass
-import org.testng.annotations.BeforeMethod
-import org.testng.annotations.Factory
+import org.gradle.api.internal.tasks.testing.*
+import org.testng.annotations.*
+
 import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.internal.id.LongIdGenerator
-import org.junit.Ignore
-import org.gradle.api.internal.tasks.testing.TestCompleteEvent
-import org.gradle.api.internal.tasks.testing.TestStartEvent
-import org.gradle.logging.StandardOutputRedirector
-import org.testng.annotations.AfterMethod
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
 
 @RunWith(JMock.class)
 class TestNGTestClassProcessorTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TemporaryFolder reportDir = new TemporaryFolder();
+    @Rule public final TemporaryFolder resultsDir = new TemporaryFolder();
     private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final TestNGOptions options = new TestNGOptions(tmpDir.dir)
-    private final TestNGTestClassProcessor processor = new TestNGTestClassProcessor(tmpDir.dir, options, [], new LongIdGenerator(), {} as StandardOutputRedirector);
+    private final TestNGOptions options = new TestNGOptions(reportDir.dir)
+    private final TestNGTestClassProcessor processor = new TestNGTestClassProcessor(reportDir.dir, options, [], new LongIdGenerator(), {} as StandardOutputRedirector, resultsDir.dir, true);
 
     @Test
     public void executesATestClass() {
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 df3dcde..f2d8c1c 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
@@ -79,8 +79,10 @@ public class TestNGTestFrameworkTest extends AbstractTestFrameworkTest {
         setMocks();
 
         context.checking(new Expectations() {{
-            allowing(testMock).getTestSrcDirs();  will(returnValue(testSrcDirs));
-            allowing(testMock).getTestReportDir(); will(returnValue(testReportDir));
+            allowing(testMock).getTestSrcDirs();    will(returnValue(testSrcDirs));
+            allowing(testMock).getTestReportDir();  will(returnValue(testReportDir));
+            allowing(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
+            allowing(testMock).isTestReport();      will(returnValue(false));
             allowing(serviceRegistry).get(IdGenerator.class); will(returnValue(idGeneratorMock));
             one(testngOptionsMock).setTestResources(testSrcDirs);
             one(testngOptionsMock).getSuites(temporaryDir);
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 b002d2a..1d1bbf7 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
@@ -20,9 +20,9 @@ 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.internal.reflect.Instantiator
 import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
 import org.gradle.util.SetSystemProperties
@@ -30,6 +30,7 @@ import org.junit.Rule
 import spock.lang.Specification
 import static org.gradle.util.Matchers.sameCollection
 import static org.gradle.util.WrapUtil.toLinkedSet
+import org.gradle.api.tasks.compile.JavaCompile
 
 /**
  * @author Hans Dockter
@@ -39,7 +40,7 @@ class JavaBasePluginTest extends Specification {
     @Rule
     public SetSystemProperties sysProperties = new SetSystemProperties()
     private final Project project = HelperUtil.createRootProject()
-    private final JavaBasePlugin javaBasePlugin = new JavaBasePlugin()
+    private final JavaBasePlugin javaBasePlugin = new JavaBasePlugin(project.services.get(Instantiator))
 
     void appliesBasePluginsAndAddsConventionObject() {
         when:
@@ -73,7 +74,7 @@ class JavaBasePluginTest extends Specification {
 
         def compileJava = project.tasks['compileCustomJava']
         compileJava.description == 'Compiles the custom Java source.'
-        compileJava instanceof Compile
+        compileJava instanceof JavaCompile
         Matchers.dependsOn().matches(compileJava)
         compileJava.classpath.is(project.sourceSets.custom.compileClasspath)
         compileJava.destinationDir == project.sourceSets.custom.output.classesDir
@@ -135,7 +136,7 @@ class JavaBasePluginTest extends Specification {
         javaBasePlugin.apply(project)
 
         then:
-        def compile = project.task('customCompile', type: Compile)
+        def compile = project.task('customCompile', type: JavaCompile)
         compile.sourceCompatibility == project.sourceCompatibility.toString()
 
         def test = project.task('customTest', type: Test.class)
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 351357d..bd6e297 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
@@ -21,6 +21,7 @@ import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TemporaryFolder
@@ -38,7 +39,7 @@ import static org.junit.Assert.assertThat
 class JavaPluginConventionTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
     private DefaultProject project = HelperUtil.createRootProject()
-    private File testDir = project.projectDir
+    private Instantiator instantiator = project.services.get(Instantiator)
     private JavaPluginConvention convention
 
     @Rule
@@ -46,7 +47,7 @@ class JavaPluginConventionTest {
 
     @Before public void setUp() {
         project.plugins.apply(ReportingBasePlugin)
-        convention = new JavaPluginConvention(project)
+        convention = new JavaPluginConvention(project, instantiator)
     }
 
     @Test public void defaultValues() {
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 35070db..bd00b14 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
@@ -26,12 +26,13 @@ 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
-import org.gradle.api.tasks.compile.Compile
+import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
 import org.gradle.util.HelperUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
+
 import static org.gradle.util.Matchers.*
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
@@ -141,7 +142,7 @@ class JavaPluginTest {
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_JAVA_TASK_NAME]
-        assertThat(task, instanceOf(Compile))
+        assertThat(task, instanceOf(JavaCompile))
         assertThat(task, dependsOn())
         assertThat(task.classpath, sameInstance(project.sourceSets.main.compileClasspath))
         assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.classesDir))
@@ -158,7 +159,7 @@ class JavaPluginTest {
         assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME]
-        assertThat(task, instanceOf(Compile))
+        assertThat(task, instanceOf(JavaCompile))
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.classpath, sameInstance(project.sourceSets.test.compileClasspath))
         assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.classesDir))
@@ -168,13 +169,6 @@ class JavaPluginTest {
         assertThat(task, instanceOf(DefaultTask))
         assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME))
 
-        task = project.tasks[JavaPlugin.TEST_TASK_NAME]
-        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.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))
@@ -219,6 +213,39 @@ class JavaPluginTest {
         assertThat(task, dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
     }
 
+    @Test void "configures test task"() {
+        javaPlugin.apply(project)
+
+        //when
+        def task = project.tasks[JavaPlugin.TEST_TASK_NAME]
+
+        //then
+        assert task instanceof org.gradle.api.tasks.testing.Test
+        assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
+        assert task.classpath == project.sourceSets.test.runtimeClasspath
+        assert task.testClassesDir == project.sourceSets.test.output.classesDir
+        assert task.workingDir == project.projectDir
+        assert task.testReport //by default (JUnit), the report is 'on'
+    }
+
+    @Test void "configures test task for testNG"() {
+        javaPlugin.apply(project)
+        def task = project.tasks[JavaPlugin.TEST_TASK_NAME]
+
+        //when
+        task.useTestNG()
+
+        //then
+        assert !task.testReport //for TestNG, the report is 'off' by default for now
+
+        //when
+        task.testReport = true
+        task.useTestNG()
+
+        //then
+        assert task.testReport
+    }
+
     @Test public void appliesMappingsToTasksAddedByTheBuildScript() {
         javaPlugin.apply(project);
 
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
deleted file mode 100644
index b47b413..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
+++ /dev/null
@@ -1,47 +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.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/github/GitHubDependenciesPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginTest.groovy
new file mode 100644
index 0000000..829b00f
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/github/GitHubDependenciesPluginTest.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.api.plugins.github
+
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class GitHubDependenciesPluginTest extends Specification {
+
+    Project project = HelperUtil.createRootProject()
+
+    def "can apply plugin"() {
+        when:
+        project.apply(plugin: "github-dependencies")
+
+        then:
+        project.repositories.extensions.findByType(GitHubRepositoryHandlerExtension)
+
+        when:
+        project.repositories {
+            github.downloads("user1")
+            github {
+                downloads {
+                    user "user2"
+                    credentials {
+                        username "foo"
+                        password "bar"
+                    }
+                }
+            }
+        }
+
+        then:
+        project.repositories.size() == 2
+    }
+}
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
deleted file mode 100644
index 586787f..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
+++ /dev/null
@@ -1,132 +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.reporting.internal
-
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.internal.AsmBackedClassGenerator
-import org.gradle.api.internal.ClassGeneratorBackedInstantiator
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.internal.reflect.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/tasks/bundling/JarTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy
index e744868..1216a14 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy
@@ -29,7 +29,6 @@ class JarTest extends AbstractArchiveTaskTest {
     Jar jar
 
     @Before public void setUp()  {
-        super.setUp()
         jar = createTask(Jar)
         configure(jar)
     }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy
index f36c810..5eae4de 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy
@@ -27,10 +27,7 @@ class WarTest extends AbstractArchiveTaskTest {
 
     War war
 
-    Map filesFromDepencencyManager
-
     @Before public void setUp() {
-        super.setUp()
         war = createTask(War)
         configure(war)
     }
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 21213d4..c78f947 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
@@ -43,11 +43,18 @@ public class AbstractOptionsTest extends Specification {
         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])
+    def "option class can extend another option class"() {
+        def options = new DeeplyInheritedOptions(intProp: 42, stringProp: "new value", objectProp: [21], deepProp: "deep")
 
         expect:
-        options.optionMap() == [:]
+        options.optionMap() == [intProp: 42, stringProp: "new value", objectProp: [21], deepProp: "deep"]
+    }
+
+    def "skips properties declared in decorated class generated by instantiator"() {
+        def options = new TestOptions_Decorated(intProp: 42, stringProp: "new value", objectProp: [21], decoratedProp: "decorated")
+
+        expect:
+        options.optionMap() == [intProp: 42, stringProp: "new value", objectProp: [21]]
     }
 
     static class TestOptions extends AbstractOptions {
@@ -61,8 +68,15 @@ public class AbstractOptionsTest extends Specification {
         String stringProp = "initial value"
         Object objectProp
 
-        Map fieldName2AntMap() {
-            [intProp: 'intProp2', objectProp: 'objectProp2']
+        @Override
+        protected String getAntPropertyName(String fieldName) {
+            if (fieldName == "intProp") {
+                return "intProp2"
+            }
+            if (fieldName == "objectProp") {
+                return "objectProp2"
+            }
+            return fieldName
         }
     }
 
@@ -72,11 +86,21 @@ public class AbstractOptionsTest extends Specification {
         Object objectProp
 
         @Override
-        Map fieldValue2AntMap() {
-            [stringProp: { stringProp * 2 }]
+        protected Object getAntPropertyValue(String fieldName, Object value) {
+            if (fieldName == "stringProp") {
+                return stringProp * 2
+            }
+            return value
         }
     }
 
-    static class IndirectlyExtendingOptions extends TestOptions {}
+    static class DeeplyInheritedOptions extends TestOptions {
+      String deepProp
+    }
+
+    @SuppressWarnings("ClassName")
+    static class TestOptions_Decorated extends TestOptions {
+        String decoratedProp
+    }
 }
 
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 35023ce..21d04a0 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
@@ -70,7 +70,7 @@ class CompileOptionsTest {
         Map nullables = [
                 encoding: 'encoding',
                 compiler: 'compiler',
-                bootClasspath: 'bootclasspath',
+                bootClasspath: 'bootClasspath',
                 extensionDirs: 'extdirs'
         ]
         nullables.each {String field, String antProperty ->
@@ -86,9 +86,9 @@ class CompileOptionsTest {
 
     @Test public void testOptionMapWithTrueFalseValues() {
         Map booleans = [
-                failOnError: 'failonerror',
+                failOnError: 'failOnError',
                 verbose: 'verbose',
-                listFiles: 'listfiles',
+                listFiles: 'listFiles',
                 deprecation: 'deprecation',
                 warnings: 'nowarn',
                 debug: 'debug',
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
deleted file mode 100644
index a2302d1..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
+++ /dev/null
@@ -1,90 +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.internal.ConventionTask;
-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;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import org.gradle.api.internal.tasks.compile.Compiler;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class CompileTest extends AbstractCompileTest {
-    private Compile compile;
-
-    private Compiler<JavaCompileSpec> compilerMock;
-
-    private Mockery context = new JUnit4GroovyMockery();
-
-    @Before public void setUp()  {
-        super.setUp();
-        compile = createTask(Compile.class);
-        compilerMock = context.mock(Compiler.class);
-        compile.setJavaCompiler(compilerMock);
-
-        GFileUtils.touch(new File(srcDir, "incl/file.java"));
-    }
-           
-    public ConventionTask getTask() {
-        return compile;
-    }
-
-    public void testExecute(final int numFilesCompiled) {
-        setUpMocksAndAttributes(compile);
-        context.checking(new Expectations() {{
-            WorkResult result = context.mock(WorkResult.class);
-
-            one(compilerMock).execute(with(IsNull.<JavaCompileSpec>notNullValue()));
-            will(returnValue(result));
-            allowing(result).getDidWork();
-            will(returnValue(numFilesCompiled > 0));
-        }});
-        compile.compile();
-    }
-
-    @Test
-    public void testExecuteDoingWork() {
-        testExecute(7);
-        assertTrue(compile.getDidWork());
-    }
-
-    @Test
-    public void testExecuteNotDoingWork() {
-        testExecute(0);
-        assertFalse(compile.getDidWork());
-    }
-
-    // todo We need to do this to make the compiler happy. We need to file a Jira to Groovy.
-    public Compile getCompile() {
-        return compile;
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy
index a90aee6..e0ba8aa 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy
@@ -16,41 +16,31 @@
  
 package org.gradle.api.tasks.compile
 
-import static org.junit.Assert.*
-import org.junit.Before
-import org.junit.Test;
+import org.junit.Test
 
 /**
  * @author Hans Dockter
  */
 class DebugOptionsTest {
-    static final String TEST_DEBUG_LEVEL = 'testDebugLevel'
-    static final String DEBUG_LEVEL_PROPERTY_NAME = 'debugLevel'
-    static final String DEBUG_LEVEL_ANT_PROPERTY_NAME = 'debuglevel'
-
-    DebugOptions debugOptions
-
-    @Before public void setUp()  {
-        debugOptions = new DebugOptions()
-    }
+    DebugOptions debugOptions = new DebugOptions()
 
     @Test public void testDebugOptions() {
-        assertNull(debugOptions.debugLevel)
+        assert debugOptions.debugLevel == null
     }
 
     @Test public void testOptionMap() {
         Map optionMap = debugOptions.optionMap()
-        assertEquals(0, optionMap.size())
+        assert optionMap.isEmpty()
 
-        debugOptions.debugLevel = TEST_DEBUG_LEVEL
+        debugOptions.debugLevel = "extreme"
         optionMap = debugOptions.optionMap()
-        assertEquals(1, optionMap.size())
-        assertEquals(optionMap[DEBUG_LEVEL_ANT_PROPERTY_NAME], TEST_DEBUG_LEVEL)
+        assert optionMap.size() == 1
+        assert optionMap.debugLevel == "extreme"
     }
 
     @Test public void testDefine() {
         debugOptions.debugLevel = null
-        debugOptions.define((DEBUG_LEVEL_PROPERTY_NAME): TEST_DEBUG_LEVEL)
-        assertEquals(TEST_DEBUG_LEVEL, debugOptions.debugLevel)
+        debugOptions.define(debugLevel: "extreme")
+        assert debugOptions.debugLevel == "extreme"
     }
 }
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 1fd1453..e6a8991 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
@@ -18,13 +18,13 @@ package org.gradle.api.tasks.compile
 
 import static org.junit.Assert.*
 import org.junit.Before
-import org.junit.Test;
+import org.junit.Test
 
 /**
  * @author Hans Dockter
  */
 class ForkOptionsTest {
-    static final Map PROPS = [executable: 'executable', memoryInitialSize: 'memoryInitialSize', memoryMaximumSize: 'memoryMaximumSize', tempDir: 'tempdir']
+    static final List PROPS = ['executable', 'memoryInitialSize', 'memoryMaximumSize', 'tempDir']
     
     ForkOptions forkOptions
 
@@ -45,17 +45,14 @@ class ForkOptionsTest {
     @Test public void testOptionMap() {
         Map optionMap = forkOptions.optionMap()
         assertEquals(0, optionMap.size())
-        PROPS.keySet().each { forkOptions."$it" = "${it}Value" }
+        PROPS.each { forkOptions."$it" = "${it}Value" }
         optionMap = forkOptions.optionMap()
         assertEquals(4, optionMap.size())
-        PROPS.keySet().each {assertEquals("${it}Value" as String, optionMap[PROPS[it]])}
+        PROPS.each { assert optionMap[it] == "${it}Value" as String }
     }
 
     @Test public void testDefine() {
-        forkOptions.define(PROPS.keySet().inject([:]) { Map map, String prop ->
-            map[prop] = "${prop}Value" as String
-            map
-        })
-        PROPS.keySet().each {assertEquals("${it}Value" as String, forkOptions."${it}")}
+        forkOptions.define(PROPS.collectEntries { [it, "${it}Value" as String ] })
+        PROPS.each { assert forkOptions."${it}" == "${it}Value" as String }
     }
 }
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 8a358d6..f88fedf 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
@@ -52,9 +52,9 @@ class GroovyCompileOptionsTest {
 
     @Test public void testOptionMapWithTrueFalseValues() {
         Map booleans = [
-                failOnError: 'failonerror',
+                failOnError: 'failOnError',
                 verbose: 'verbose',
-                listFiles: 'listfiles',
+                listFiles: 'listFiles',
                 fork: 'fork',
                 includeJavaRuntime: 'includeJavaRuntime'
         ]
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 0b49d01..d3896b5 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
@@ -60,7 +60,6 @@ public class GroovyCompileTest extends AbstractCompileTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         testObj = createTask(GroovyCompile.class);
         groovyCompilerMock = context.mock(Compiler.class);
         testObj.setCompiler(groovyCompilerMock);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java
new file mode 100644
index 0000000..0e60325
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/JavaCompileTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.ConventionTask;
+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;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.gradle.api.internal.tasks.compile.Compiler;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class JavaCompileTest extends AbstractCompileTest {
+    private JavaCompile compile;
+
+    private Compiler<JavaCompileSpec> compilerMock;
+
+    private Mockery context = new JUnit4GroovyMockery();
+
+    @Before
+    public void setUp()  {
+        compile = createTask(JavaCompile.class);
+        compilerMock = context.mock(Compiler.class);
+        compile.setJavaCompiler(compilerMock);
+
+        GFileUtils.touch(new File(srcDir, "incl/file.java"));
+    }
+           
+    public ConventionTask getTask() {
+        return compile;
+    }
+
+    public void testExecute(final int numFilesCompiled) {
+        setUpMocksAndAttributes(compile);
+        context.checking(new Expectations() {{
+            WorkResult result = context.mock(WorkResult.class);
+
+            one(compilerMock).execute(with(IsNull.<JavaCompileSpec>notNullValue()));
+            will(returnValue(result));
+            allowing(result).getDidWork();
+            will(returnValue(numFilesCompiled > 0));
+        }});
+        compile.compile();
+    }
+
+    @Test
+    public void testExecuteDoingWork() {
+        testExecute(7);
+        assertTrue(compile.getDidWork());
+    }
+
+    @Test
+    public void testExecuteNotDoingWork() {
+        testExecute(0);
+        assertFalse(compile.getDidWork());
+    }
+
+    public JavaCompile getCompile() {
+        return compile;
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java
index 8ce02f1..111e76d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java
@@ -36,7 +36,6 @@ public class GroovydocTest extends AbstractConventionTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         groovydoc = createTask(Groovydoc.class);
     }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
index 746ab93..3b5b20b 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
@@ -58,7 +58,6 @@ public class JavadocTest extends AbstractConventionTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         task = createTask(Javadoc.class);
         task.setClasspath(configurationMock);
         task.setExecutable(executable);
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 3a7c5dd..baf1884 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
@@ -86,8 +86,6 @@ public class TestTest extends AbstractConventionTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
-
         File rootDir = getProject().getProjectDir();
         classesDir = new File(rootDir, "testClassesDir");
         File classfile = new File(classesDir, "FileTest.class");
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
index d2ef62b..4b6d6c6 100644
--- 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
@@ -46,7 +46,6 @@ public class WrapperTest extends AbstractTaskTest {
 
     @Before
     public void setUp() {
-        super.setUp();
         wrapper = createTask(Wrapper.class);
         wrapper.setGradleVersion("1.0");
         targetWrapperJarPath = "gradle/wrapper";
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy
new file mode 100644
index 0000000..fc4b9dd
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriterTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.internal
+
+import org.gradle.external.javadoc.JavadocOptionFileOption
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class JavadocOptionFileWriterTest extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder
+
+    JavadocOptionFile optionfile = Mock()
+    JavadocOptionFileWriter javadocOptionFileWriter = new JavadocOptionFileWriter(optionfile)
+
+    def "writes locale option first if set"() {
+        setup:
+        def tempFile = temporaryFolder.createFile("optionFile")
+        def optionsMap = createOptionsMap()
+        when:
+        _ * optionfile.options >> optionsMap
+        _ * optionfile.getSourceNames() >> new OptionLessStringsJavadocOptionFileOption();
+        javadocOptionFileWriter.write(tempFile)
+        then:
+        tempFile.text == toPlatformLineSeparators("""-key1 'value1'
+-key2 'value2'
+-key3 'value3'
+""")
+        when:
+        optionsMap.put("locale", new StringJavadocOptionFileOption("locale", "alocale"));
+        and:
+        javadocOptionFileWriter.write(tempFile)
+        then:
+        tempFile.text == toPlatformLineSeparators("""-locale 'alocale'
+-key1 'value1'
+-key2 'value2'
+-key3 'value3'
+""")
+    }
+
+    Map<String, JavadocOptionFileOption> createOptionsMap() {
+        Map<String, JavadocOptionFileOption> options = new HashMap<String, JavadocOptionFileOption>();
+        options.put("key1", new StringJavadocOptionFileOption("key1", "value1"))
+        options.put("key2", new StringJavadocOptionFileOption("key2", "value2"))
+        options.put("key3", new StringJavadocOptionFileOption("key3", "value3"))
+        return options
+    }
+}
diff --git a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
index 15a9f5f..fd89800 100644
--- a/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
+++ b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
@@ -19,6 +19,7 @@ package org.gradle.api.tasks.compile;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.tasks.AbstractConventionTaskTest;
 import org.gradle.util.WrapUtil;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.io.File;
@@ -45,9 +46,8 @@ public abstract class AbstractCompileTest extends AbstractConventionTaskTest {
 
     protected abstract AbstractCompile getCompile();
 
-    @Override
-    public void setUp() {
-        super.setUp();
+    @Before
+    public final void setUpDirs() {
         destDir = getProject().file("destDir");
         depCacheDir = getProject().file("depCache");
         srcDir = getProject().file("src");
diff --git a/subprojects/publish/publish.gradle b/subprojects/publish/publish.gradle
new file mode 100644
index 0000000..239b19c
--- /dev/null
+++ b/subprojects/publish/publish.gradle
@@ -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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+    compile project(':core')
+}
+
+useTestFixtures()
+useClassycle()
\ No newline at end of file
diff --git a/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/PublishAutoTestedSamplesIntegrationTest.groovy b/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/PublishAutoTestedSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..8558c7c
--- /dev/null
+++ b/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/PublishAutoTestedSamplesIntegrationTest.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.api.publish
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+class PublishAutoTestedSamplesIntegrationTest extends AbstractAutoTestedSamplesTest{
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/publish/src/main")
+    }
+
+}
+
+
diff --git a/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/plugins/PublishingPluginIntegTest.groovy b/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/plugins/PublishingPluginIntegTest.groovy
new file mode 100644
index 0000000..881a4c5
--- /dev/null
+++ b/subprojects/publish/src/integTest/groovy/org/gradle/api/publish/plugins/PublishingPluginIntegTest.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.api.publish.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class PublishingPluginIntegTest extends WellBehavedPluginTest {
+
+    @Override
+    String getMainTask() {
+        "publish"
+    }
+
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/Publication.java b/subprojects/publish/src/main/java/org/gradle/api/publish/Publication.java
new file mode 100644
index 0000000..25aaf9a
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/Publication.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.publish;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A publication is a description of a consumable representation of one or more artifacts, and possibly associated metadata.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public interface Publication extends Named {
+
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.java b/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.java
new file mode 100644
index 0000000..9512f27
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.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.publish;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A {@code PublicationContainer} is responsible for declaring and managing publications.
+ *
+ * Publications cannot be added to a publication container by users at this time. Publication plugins
+ * are responsible for creating {@link Publication} instances in the container.
+ *
+ * See the documentation for the Ivy Publishing plugin for more information.
+ *
+ * @since 1.3
+ * @see Publication
+ */
+ at Incubating
+public interface PublicationContainer extends NamedDomainObjectSet<Publication> {
+
+    /**
+     * {@inheritDoc}
+     */
+    Publication getByName(String name) throws UnknownPublicationException;
+
+    /**
+     * {@inheritDoc}
+     */
+    Publication getAt(String name) throws UnknownPublicationException;
+
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java b/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
new file mode 100644
index 0000000..d386ba7
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
@@ -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.publish;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+
+/**
+ * The configuration of how to “publish” the different components of a project.
+ * <p>
+ * This new publishing mechanism will eventually replace the current mechanism of upload tasks and configurations. At this time, it is an
+ * incubating feature and under development.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public interface PublishingExtension {
+
+    /**
+     * The name of this extension when installed by the {@link org.gradle.api.publish.plugins.PublishingPlugin} ({@value}).
+     */
+    String NAME = "publishing";
+
+    /**
+     * The container of possible repositories to publish to.
+     * <p>
+     * See {@link #repositories(org.gradle.api.Action)} for more information.
+     *
+     * @return The container of possible repositories to publish to.
+     */
+    RepositoryHandler getRepositories();
+
+    /**
+     * Configures the container of possible repositories to publish to.
+     *
+     * <pre autoTested="true">
+     * apply plugin: 'publishing'
+     *
+     * publishing {
+     *   repositories {
+     *     // Create an ivy publication destination named “releases”
+     *     ivy {
+     *       name "releases"
+     *       url "http://my.org/ivy-repos/releases"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * The {@code repositories} block is backed by a {@link RepositoryHandler}, which is the same DSL as that that is used for declaring repositories to consume dependencies from. However,
+     * certain types of repositories that can be created by the repository handler are not valid for publishing, such as {@link org.gradle.api.artifacts.dsl.RepositoryHandler#mavenCentral()}.
+     * <p>
+     * At this time, only repositories created by the {@code ivy()} factory method have any effect. Please see {@link org.gradle.api.publish.ivy.IvyPublication}
+     * for information on how this can be used for publishing to Ivy repositories.
+     *
+     * @param configure The action to configure the container of repositories with.
+     */
+    void repositories(Action<? super RepositoryHandler> configure);
+
+    /**
+     * The publications of the project.
+     * <p>
+     * See {@link #publications(org.gradle.api.Action)} for more information.
+     *
+     * @return The publications of this project.
+     */
+    PublicationContainer getPublications();
+
+    /**
+     * Configures the publications of this project.
+     * <p>
+     * The publications container defines the outgoing publications of the project. That is, the consumable representations of things produced
+     * by building the project. An example of a publication would be an Ivy Module (i.e. {@code ivy.xml} and artifacts), or
+     * Maven Project (i.e. {@code pom.xml} and artifacts).
+     * <p>
+     * The ability to create different kinds of publications is provided by different plugins. The “publishing” plugin itself does not provide a way
+     * to create publications.
+     * <p>
+     * Please see {@link org.gradle.api.publish.ivy.IvyPublication} for information on publishing in the Ivy format.
+     * At this time it is not possible to publish in the Maven format with this mechanism.
+     *
+     * @param configure The action or closure to configure the publications with.
+     */
+    void publications(Action<? super PublicationContainer> configure);
+
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.java b/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.java
new file mode 100644
index 0000000..c54fcdc
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.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.publish;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.UnknownDomainObjectException;
+
+/**
+ * An {@code UnknownPublicationException} is thrown when a configuration referenced by name cannot be found.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public class UnknownPublicationException extends UnknownDomainObjectException {
+    public UnknownPublicationException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.java
new file mode 100644
index 0000000..2f78c02
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.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.publish.internal;
+
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.publish.Publication;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.UnknownPublicationException;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultPublicationContainer extends DefaultNamedDomainObjectSet<Publication> implements PublicationContainer {
+
+    public DefaultPublicationContainer(Instantiator instantiator) {
+        super(Publication.class, instantiator);
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownPublicationException(String.format("Publication with name '%s' not found", name));
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.java
new file mode 100644
index 0000000..23c7af1
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.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.publish.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+
+public class DefaultPublishingExtension implements PublishingExtension {
+
+    private final RepositoryHandler repositories;
+    private final PublicationContainer publications;
+
+    public DefaultPublishingExtension(RepositoryHandler repositories, PublicationContainer publications) {
+        this.repositories = repositories;
+        this.publications = publications;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositories;
+    }
+
+    public void repositories(Action<? super RepositoryHandler> configure) {
+        configure.execute(repositories);
+    }
+
+    public PublicationContainer getPublications() {
+        return publications;
+    }
+
+    public void publications(Action<? super PublicationContainer> configure) {
+        configure.execute(publications);
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/package-info.java b/subprojects/publish/src/main/java/org/gradle/api/publish/package-info.java
new file mode 100644
index 0000000..67cbe27
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/package-info.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.
+ */
+
+/**
+ * Classes that deal with publishing artifacts.
+ *
+ * @since 1.3
+ */
+ at Incubating
+package org.gradle.api.publish;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
new file mode 100644
index 0000000..e01ae7e
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.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.api.publish.plugins;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.internal.DefaultPublicationContainer;
+import org.gradle.api.publish.internal.DefaultPublishingExtension;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+
+/**
+ * Installs a {@link org.gradle.api.publish.PublishingExtension} with name {@value org.gradle.api.publish.PublishingExtension#NAME}.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public class PublishingPlugin implements Plugin<Project> {
+
+    public static final String PUBLISH_LIFECYCLE_TASK_NAME = "publish";
+
+    private final Instantiator instantiator;
+    private final Factory<ArtifactPublicationServices> artifactPublicationServicesFactory;
+
+    @Inject
+    public PublishingPlugin(Factory<ArtifactPublicationServices> artifactPublicationServicesFactory, Instantiator instantiator) {
+        this.artifactPublicationServicesFactory = artifactPublicationServicesFactory;
+        this.instantiator = instantiator;
+    }
+
+    public void apply(Project project) {
+        RepositoryHandler repositories = artifactPublicationServicesFactory.create().getRepositoryHandler();
+        PublicationContainer publications = instantiator.newInstance(DefaultPublicationContainer.class, instantiator);
+        project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
+
+        project.getTasks().add(PUBLISH_LIFECYCLE_TASK_NAME);
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/package-info.java b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/package-info.java
new file mode 100644
index 0000000..e626033
--- /dev/null
+++ b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/package-info.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.
+ */
+
+/**
+ * Publishing plugins.
+ *
+ * @since 1.3
+ */
+ at Incubating
+package org.gradle.api.publish.plugins;
+
+import org.gradle.api.Incubating;
\ No newline at end of file
diff --git a/subprojects/publish/src/main/resources/META-INF/gradle-plugins/publishing.properties b/subprojects/publish/src/main/resources/META-INF/gradle-plugins/publishing.properties
new file mode 100644
index 0000000..b17eb1d
--- /dev/null
+++ b/subprojects/publish/src/main/resources/META-INF/gradle-plugins/publishing.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.publish.plugins.PublishingPlugin
\ No newline at end of file
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
new file mode 100644
index 0000000..f9e9d0c
--- /dev/null
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
@@ -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.publish.internal
+
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublicationContainer
+import org.gradle.api.publish.UnknownPublicationException
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.ConfigureUtil
+import spock.lang.Ignore
+import spock.lang.Specification
+
+class DefaultPublicationContainerTest extends Specification {
+
+    Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+    PublicationContainer container = instantiator.newInstance(DefaultPublicationContainer, instantiator)
+
+    def "right exception is thrown on unknown access"() {
+        given:
+        container.add(publication("foo"))
+
+        expect:
+        container.foo instanceof Publication
+
+        when:
+        container.getByName("notHere")
+
+        then:
+        def e = thrown(UnknownPublicationException)
+        e.message == "Publication with name 'notHere' not found"
+    }
+
+    @Ignore("This throws an MME, which is not as nice for the user")
+    def "right exception is thrown on unknown access as method"() {
+        given:
+        container.add(publication("foo"))
+
+        expect:
+        container.foo { } instanceof Publication
+
+        when:
+        ConfigureUtil.configure({ notHere { } }, container)
+
+        then:
+        def e = thrown(UnknownPublicationException)
+        e.message == "Publication with name 'notHere' not found"
+    }
+
+    Publication publication(String name) {
+        new Publication() {
+            String getName() {
+                name
+            }
+        }
+    }
+}
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/plugins/PublishingPluginTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/plugins/PublishingPluginTest.groovy
new file mode 100644
index 0000000..1ebcf6f
--- /dev/null
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/plugins/PublishingPluginTest.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.publish.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublicationContainer
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class PublishingPluginTest extends Specification {
+
+    Project project = HelperUtil.createRootProject()
+    PublishingExtension extension
+
+    def setup() {
+        project.plugins.apply(PublishingPlugin)
+        extension = project.extensions.getByType(PublishingExtension)
+    }
+
+    def "publishing extension is installed"() {
+        expect:
+        extension.publications != null
+        extension.publications instanceof PublicationContainer
+
+        extension.repositories != null
+        extension.repositories instanceof RepositoryHandler
+    }
+
+    def "can create repo"() {
+        when:
+        extension.repositories {
+            mavenCentral()
+        }
+
+        then:
+        extension.repositories.size() == 1
+        project.repositories.size() == 0 // ensure we didn't somehow create a resolution repo
+    }
+
+    def "can add publication"() {
+        given:
+        def publication = new Publication() {
+            String getName() { "foo" }
+        }
+
+        when:
+        extension.publications.add(publication)
+
+        then:
+        extension.publications.size() == 1
+        extension.publications.toList().first().is(publication)
+    }
+
+    def "lifecycle task created"() {
+        expect:
+        project.tasks[PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME] != null
+    }
+}
diff --git a/subprojects/reporting/reporting.gradle b/subprojects/reporting/reporting.gradle
new file mode 100644
index 0000000..b199a61
--- /dev/null
+++ b/subprojects/reporting/reporting.gradle
@@ -0,0 +1,6 @@
+dependencies {
+    groovy libraries.groovy
+    compile project(':core')
+}
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java b/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
new file mode 100644
index 0000000..1da1d3f
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.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;
+
+import org.gradle.api.Plugin;
+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>
+ *
+ * <p>This plugin adds the following convention objects to the project:</p>
+ *
+ * <ul>
+ *
+ * <li>{@link ReportingBasePluginConvention}</li>
+ *
+ * </ul>
+ */
+public class ReportingBasePlugin implements Plugin<ProjectInternal> {
+    public void apply(ProjectInternal project) {
+        Convention convention = project.getConvention();
+        ReportingExtension extension = project.getExtensions().create(ReportingExtension.NAME, ReportingExtension.class, project);
+
+        // 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/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Report.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Report.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportContainer.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Reporting.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Reporting.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Reporting.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
similarity index 100%
rename from subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
rename to subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/package-info.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/package-info.java
new file mode 100644
index 0000000..cc239bb
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/package-info.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.
+ */
+
+/**
+ * Classes for reporting
+ */
+package org.gradle.api.reporting;
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy
rename to subprojects/reporting/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
new file mode 100644
index 0000000..bc3ad43
--- /dev/null
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.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.api.reporting.internal
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.api.reporting.Report
+import org.gradle.api.reporting.ReportContainer
+import spock.lang.Specification
+import org.gradle.internal.reflect.ObjectInstantiationException
+
+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) {
+        try {
+            instantiator.newInstance(TestReportContainer, c)
+        } catch (ObjectInstantiationException e) {
+            throw e.cause
+        }
+    }
+
+    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/reporting/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy
rename to subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
rename to subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
diff --git a/subprojects/scala/scala.gradle b/subprojects/scala/scala.gradle
index cb618f5..ad6e0ce 100644
--- a/subprojects/scala/scala.gradle
+++ b/subprojects/scala/scala.gradle
@@ -13,13 +13,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+apply from: "$rootDir/gradle/providedConfiguration.gradle"
+
 dependencies {
     groovy libraries.groovy
 
-    compile project(':core')
-    compile project(':plugins')
+    compile project(":core")
+    compile project(":plugins")
+
+    // keep in sync with ScalaBasePlugin code
+    provided("com.typesafe.zinc:zinc:0.2.0")
 
     testCompile libraries.slf4j_api
 }
 
 useTestFixtures(project: ":plugins") // includes core test fixtures
+
+if (!javaVersion.java6Compatible) {
+    sourceSets.all {
+        groovy.exclude '**/jdk6/**'
+    }
+}
+
+configure([integTest, daemonIntegTest]) {
+    jvmArgs "-XX:MaxPermSize=1g" // AntInProcessScalaCompilerIntegrationTest needs lots of permgen
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..22cfac8
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntForkingScalaCompilerIntegrationTest.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.scala.compile
+
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions(["2.8.2", "2.9.2"])
+class AntForkingScalaCompilerIntegrationTest extends BasicScalaCompilerIntegrationTest {
+    def setup() {
+        distribution.requireIsolatedDaemons()
+    }
+
+    String compilerConfiguration() {
+        '''
+compileScala.scalaCompileOptions.with {
+    useAnt = true
+    fork = true
+}
+'''
+    }
+
+    String logStatement() {
+        "Compiling with Ant scalac task"
+    }
+
+    String getErrorOutput() {
+        return result.output
+    }
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..c387bbc
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/AntInProcessScalaCompilerIntegrationTest.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.scala.compile
+
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions(["2.8.2", "2.9.2"])
+class AntInProcessScalaCompilerIntegrationTest extends BasicScalaCompilerIntegrationTest {
+    def setup() {
+        distribution.requireIsolatedDaemons()
+    }
+
+    String compilerConfiguration() {
+        '''
+compileScala.scalaCompileOptions.with {
+    useAnt = true
+}
+'''
+    }
+
+    String logStatement() {
+        "Compiling with Ant scalac task"
+    }
+
+    String getErrorOutput() {
+        return result.output
+    }
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..6181510
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/BasicScalaCompilerIntegrationTest.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.scala.compile
+
+import org.gradle.integtests.fixtures.ClassFile
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.util.VersionNumber
+
+abstract class BasicScalaCompilerIntegrationTest extends MultiVersionIntegrationSpec {
+    def setup() {
+        args("-i", "-PscalaVersion=$version")
+        buildFile << buildScript()
+        buildFile <<
+"""
+DeprecationLogger.whileDisabled {
+    ${compilerConfiguration()}
+}
+"""
+    }
+
+    def compileGoodCode() {
+        given:
+        goodCode()
+
+        expect:
+        succeeds("compileScala")
+        output.contains(logStatement())
+        file("build/classes/main/compile/test/Person.class").exists()
+    }
+
+    def compileBadCodeBreaksTheBuild() {
+        given:
+        badCode()
+
+        expect:
+        fails("compileScala")
+        output.contains(logStatement())
+        errorOutput.contains("type mismatch")
+        file("build/classes/main").assertHasDescendants()
+    }
+
+    def compileBadCodeWithoutFailing() {
+        given:
+        badCode()
+
+        and:
+        buildFile <<
+"""
+compileScala.scalaCompileOptions.failOnError = false
+"""
+
+        expect:
+        succeeds("compileScala")
+        output.contains(logStatement())
+        errorOutput.contains("type mismatch")
+        file("build/classes/main").assertHasDescendants()
+    }
+
+    def compileWithSpecifiedEncoding() {
+        given:
+        goodCodeEncodedWith("ISO8859_7")
+
+        and:
+        buildFile <<
+"""
+apply plugin: "application"
+mainClassName = "Main"
+compileScala.scalaCompileOptions.encoding = "ISO8859_7"
+"""
+
+        expect:
+        succeeds("run")
+        output.contains(logStatement())
+        file("encoded.out").getText("utf-8") == "\u03b1\u03b2\u03b3"
+    }
+
+    def compilesWithSpecifiedDebugSettings() {
+        given:
+        goodCode()
+
+        when:
+        run("compileScala")
+
+        then:
+        def fullDebug = classFile("build/classes/main/compile/test/Person.class")
+        fullDebug.debugIncludesSourceFile
+        fullDebug.debugIncludesLineNumbers
+        fullDebug.debugIncludesLocalVariables
+
+        when:
+        buildFile <<
+"""
+compileScala.scalaCompileOptions.debugLevel = "line"
+"""
+        run("compileScala")
+
+        then:
+        def linesOnly = classFile("build/classes/main/compile/test/Person.class")
+        linesOnly.debugIncludesSourceFile
+        linesOnly.debugIncludesLineNumbers
+        !linesOnly.debugIncludesLocalVariables
+
+        // older versions of scalac Ant task don't handle 'none' correctly
+        if (versionNumber < VersionNumber.parse("2.10.0-AAA")) { return }
+
+        when:
+        buildFile <<
+"""
+compileScala.scalaCompileOptions.debugLevel = "none"
+"""
+        run("compileScala")
+
+        then:
+        def noDebug = classFile("build/classes/main/compile/test/Person.class")
+        !noDebug.debugIncludesLineNumbers
+        !noDebug.debugIncludesSourceFile
+        !noDebug.debugIncludesLocalVariables
+    }
+
+    def failsWithGoodErrorMessageWhenScalaToolsNotConfigured() {
+
+    }
+
+    def buildScript() {
+"""
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+    // temporary measure for the next few hours, until Zinc 0.2-M2 has landed in Central
+    maven { url "https://oss.sonatype.org/content/repositories/releases" }
+}
+
+dependencies {
+    scalaTools "org.scala-lang:scala-compiler:$version"
+    compile "org.scala-lang:scala-library:$version"
+    compile localGroovy()
+}
+"""
+    }
+
+    abstract String compilerConfiguration()
+
+    abstract String logStatement()
+
+    def goodCode() {
+        file("src/main/scala/compile/test/Person.scala") <<
+"""
+package compile.test
+
+import scala.collection.JavaConversions._
+import org.codehaus.groovy.runtime.DefaultGroovyMethods
+
+class Person(val name: String, val age: Int) {
+    def hello() {
+        val x: java.util.Collection[Int] = List(3, 1, 2)
+        DefaultGroovyMethods.max(x)
+    }
+}
+"""
+        file("src/main/scala/compile/test/Person2.scala") <<
+"""
+package compile.test
+
+class Person2(name: String, age: Int) extends Person(name, age) {
+}
+"""
+    }
+
+    def goodCodeEncodedWith(String encoding) {
+        def code =
+"""
+import java.io.{FileOutputStream, File, OutputStreamWriter}
+
+object Main {
+    def main(args: Array[String]) {
+        // Some lowercase greek letters
+        val content = "\u03b1\u03b2\u03b3"
+        val writer = new OutputStreamWriter(new FileOutputStream(new File("encoded.out")), "utf-8")
+        writer.write(content)
+        writer.close()
+    }
+}
+"""
+        def file = file("src/main/scala/Main.scala")
+        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/scala/compile/test/Person.scala") <<
+"""
+package compile.test
+
+class Person(val name: String, val age: Int) {
+    def hello() : String = 42
+}
+"""
+    }
+
+    def classFile(String path) {
+        return new ClassFile(file(path))
+    }
+}
+
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..8f7ce4e
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/ZincScalaCompilerIntegrationTest.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.scala.compile
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.JDK5)
+class ZincScalaCompilerIntegrationTest extends AbstractIntegrationSpec {
+    def "gives sensible error when run with Java 5"() {
+        buildFile <<
+"""
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools "org.scala-lang:scala-compiler:2.9.2"
+    compile "org.scala-lang:scala-library:2.9.2"
+}
+
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.useAnt = false
+}
+"""
+
+        file("src/main/scala/Person.scala") << "class Person"
+
+        expect:
+        fails("compileScala")
+        failure.assertHasCause("To use the Zinc Scala compiler, Java 6 or higher is required.")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
new file mode 100644
index 0000000..cfe8a4b
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.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.scala.compile.jdk6
+
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
+
+ at TargetVersions(["2.10.0-RC1"])
+class AntForkingScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
+    def setup() {
+        distribution.requireIsolatedDaemons()
+    }
+
+    String compilerConfiguration() {
+        '''
+compileScala.scalaCompileOptions.with {
+    useAnt = true
+    fork = true
+}
+'''
+    }
+
+    String logStatement() {
+        "Compiling with Ant scalac task"
+    }
+
+    String getErrorOutput() {
+        return result.output
+    }
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
new file mode 100644
index 0000000..b1472d1
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.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.scala.compile.jdk6
+
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
+
+ at TargetVersions(["2.10.0-RC1"])
+class AntInProcessScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
+    def setup() {
+        distribution.requireIsolatedDaemons()
+    }
+
+    String compilerConfiguration() {
+        '''
+compileScala.scalaCompileOptions.with {
+    useAnt = true
+}
+'''
+    }
+
+    String logStatement() {
+        "Compiling with Ant scalac task"
+    }
+
+    String getErrorOutput() {
+        return result.output
+    }
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy
new file mode 100644
index 0000000..7981706
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.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.scala.compile.jdk6
+
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
+import org.junit.Rule
+
+ at TargetVersions(["2.8.2", "2.9.2", "2.10.0-RC1"])
+class ZincScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
+    @Rule TestResources testResources
+
+    String compilerConfiguration() {
+        """
+compileScala.scalaCompileOptions.with {
+    useAnt = false
+}
+        """
+    }
+
+    String logStatement() {
+        "Compiling with Zinc Scala compiler"
+    }
+
+    def compilesScalaCodeIncrementally() {
+        setup:
+        def person = file("build/classes/main/Person.class")
+        def house = file("build/classes/main/House.class")
+        def other = file("build/classes/main/Other.class")
+        run("compileScala")
+
+        when:
+        file("src/main/scala/Person.scala").delete()
+        file("src/main/scala/Person.scala") << "class Person"
+        args("-i", "-PscalaVersion=$version") // each run clears args (argh!)
+        run("compileScala")
+
+        then:
+        person.lastModified() != old(person.lastModified())
+        house.lastModified() != old(house.lastModified())
+        other.lastModified() == old(other.lastModified())
+    }
+
+    def compilesJavaCodeIncrementally() {
+        setup:
+        def person = file("build/classes/main/Person.class")
+        def house = file("build/classes/main/House.class")
+        def other = file("build/classes/main/Other.class")
+        run("compileScala")
+
+        when:
+        file("src/main/scala/Person.java").delete()
+        file("src/main/scala/Person.java") << "public class Person {}"
+        args("-i", "-PscalaVersion=$version") // each run clears args (argh!)
+        run("compileScala")
+
+        then:
+        person.lastModified() != old(person.lastModified())
+        house.lastModified() != old(house.lastModified())
+        other.lastModified() == old(other.lastModified())
+    }
+
+    def compilesIncrementallyAcrossProjectBoundaries() {
+        setup:
+        def person = file("prj1/build/classes/main/Person.class")
+        def house = file("prj2/build/classes/main/House.class")
+        def other = file("prj2/build/classes/main/Other.class")
+        run("compileScala")
+
+        when:
+        file("prj1/src/main/scala/Person.scala").delete()
+        file("prj1/src/main/scala/Person.scala") << "class Person"
+        args("-i", "-PscalaVersion=$version") // each run clears args (argh!)
+        run("compileScala")
+
+        then:
+        person.lastModified() != old(person.lastModified())
+        house.lastModified() != old(house.lastModified())
+        other.lastModified() == old(other.lastModified())
+    }
+}
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
index a9927aa..7fa9d6d 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
@@ -26,11 +26,11 @@ import spock.lang.Unroll
 class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
 
 
-    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @IgnoreIf({ AvailableJavaHomes.bestJre == null})
     @Unroll
     def "scala java cross compilation works in forking mode = #forkMode when JAVA_HOME is set to JRE"() {
         given:
-        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        def jreJavaHome = AvailableJavaHomes.bestJre
         file("src/main/scala/org/test/JavaClazz.java") << """
                     package org.test;
                     public class JavaClazz {
@@ -49,9 +49,9 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     dependencies {
-                        scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-                        scalaTools 'org.scala-lang:scala-library:2.8.1'
-                        compile    'org.scala-lang:scala-library:2.8.1'
+                        scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+                        scalaTools 'org.scala-lang:scala-library:2.9.2'
+                        compile    'org.scala-lang:scala-library:2.9.2'
                     }
 
                     compileScala{
@@ -80,9 +80,9 @@ class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
                     }
 
                     dependencies {
-                        scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-                        scalaTools 'org.scala-lang:scala-library:2.8.1'
-                        compile    'org.scala-lang:scala-library:2.8.1'
+                        scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+                        scalaTools 'org.scala-lang:scala-library:2.9.2'
+                        compile    'org.scala-lang:scala-library:2.9.2'
                     }
                     """
         def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
index a375700..bbc0f8b 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -5,7 +5,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
-    compile 'org.scala-lang:scala-library:2.8.1'
+    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+    compile 'org.scala-lang:scala-library:2.9.2'
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
index 58ce9cc..2088e30 100644
--- a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -5,8 +5,6 @@ repositories {
 }
 
 dependencies {
-    scalaTools 'org.scala-lang:scala-compiler:2.8.1'
-    scalaTools 'org.scala-lang:scala-library:2.8.1'
-
-    compile 'org.scala-lang:scala-library:2.8.1'
+    scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+    compile 'org.scala-lang:scala-library:2.9.2'
 }
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle
new file mode 100644
index 0000000..c2d8f8b
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/build.gradle
@@ -0,0 +1,26 @@
+subprojects {
+    apply plugin: "scala"
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
+        compile "org.scala-lang:scala-library:$scalaVersion"
+    }
+
+    tasks.withType(ScalaCompile) {
+        scalaCompileOptions.with {
+            useAnt = false
+            fork = true
+        }
+    }
+}
+
+project(":prj2") {
+    dependencies {
+        compile project(":prj1")
+    }
+}
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj1/src/main/scala/Person.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj1/src/main/scala/Person.scala
new file mode 100644
index 0000000..338347d
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj1/src/main/scala/Person.scala
@@ -0,0 +1 @@
+class Person(val name: String, val age: Int)
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/House.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/House.scala
new file mode 100644
index 0000000..bdf1b61
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/House.scala
@@ -0,0 +1 @@
+class House(val owner: Person)
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/Other.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/Other.scala
new file mode 100644
index 0000000..c669344
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/prj2/src/main/scala/Other.scala
@@ -0,0 +1,2 @@
+class Other
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/settings.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/settings.gradle
new file mode 100644
index 0000000..4a6c5b3
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesIncrementallyAcrossProjectBoundaries/settings.gradle
@@ -0,0 +1,2 @@
+include "prj1", "prj2"
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle
new file mode 100644
index 0000000..f1770d8
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
+    compile "org.scala-lang:scala-library:$scalaVersion"
+}
+
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.with {
+        useAnt = false
+        fork = true
+    }
+}
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/House.java b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/House.java
new file mode 100644
index 0000000..3f4cdf8
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/House.java
@@ -0,0 +1,13 @@
+import java.util.List;
+
+public class House {
+    private final Person owner;
+
+    public House(Person owner) {
+        this.owner = owner;
+    }
+
+    public Person getOwner() {
+        return owner;
+    }
+}
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Other.java b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Other.java
new file mode 100644
index 0000000..40822d0
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Other.java
@@ -0,0 +1 @@
+public class Other {}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Person.java b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Person.java
new file mode 100644
index 0000000..26af7ad
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesJavaCodeIncrementally/src/main/scala/Person.java
@@ -0,0 +1,17 @@
+public class Person {
+    private final String name;
+    private final int age;
+
+    public Person(String name, int age) {
+        this.name = name;
+        this.age = age;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getAge() {
+        return age;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle
new file mode 100644
index 0000000..f1770d8
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools "org.scala-lang:scala-compiler:$scalaVersion"
+    compile "org.scala-lang:scala-library:$scalaVersion"
+}
+
+tasks.withType(ScalaCompile) {
+    scalaCompileOptions.with {
+        useAnt = false
+        fork = true
+    }
+}
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/House.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/House.scala
new file mode 100644
index 0000000..bdf1b61
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/House.scala
@@ -0,0 +1 @@
+class House(val owner: Person)
\ No newline at end of file
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Other.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Other.scala
new file mode 100644
index 0000000..c669344
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Other.scala
@@ -0,0 +1,2 @@
+class Other
+
diff --git a/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Person.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Person.scala
new file mode 100644
index 0000000..338347d
--- /dev/null
+++ b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest/compilesScalaCodeIncrementally/src/main/scala/Person.scala
@@ -0,0 +1 @@
+class Person(val name: String, val age: Int)
\ No newline at end of file
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 dab1fc9..23efc20 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
@@ -18,13 +18,14 @@ package org.gradle.api.internal.tasks.scala
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.project.IsolatedAntBuilder
 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
+import org.gradle.util.VersionNumber
+import org.gradle.util.GUtil
 
 class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
-    private static Logger logger = LoggerFactory.getLogger(AntScalaCompiler)
+    private static final Logger LOGGER = LoggerFactory.getLogger(AntScalaCompiler)
 
     private final IsolatedAntBuilder antBuilder
     private final Iterable<File> bootclasspathFiles
@@ -43,11 +44,19 @@ class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
     }
 
     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
+        def destinationDir = spec.destinationDir
+        def scalaCompileOptions = spec.scalaCompileOptions
+
+        def backend = chooseBackend(spec)
+        def options = [destDir: destinationDir, target: backend] + scalaCompileOptions.optionMap()
+        if (scalaCompileOptions.fork) {
+            options.compilerPath = GUtil.asPath(spec.scalaClasspath)
+        }
+        def taskName = scalaCompileOptions.useCompileDaemon ? 'fsc' : 'scalac'
+        def compileClasspath = spec.classpath
+
+        LOGGER.info("Compiling with Ant scalac task.")
+        LOGGER.debug("Ant scalac task options: {}", options)
 
         antBuilder.withClasspath(spec.scalaClasspath).execute { ant ->
             taskdef(resource: 'scala/tools/ant/antlib.xml')
@@ -70,4 +79,31 @@ class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
         return { true } as WorkResult
     }
 
+    private VersionNumber sniffScalaVersion(Iterable<File> classpath) {
+        def classLoader = new URLClassLoader(classpath*.toURI()*.toURL() as URL[], (ClassLoader) null)
+        try {
+            def clazz = classLoader.loadClass("scala.util.Properties")
+            return VersionNumber.parse(clazz.scalaPropOrEmpty("maven.version.number"))
+        } catch (ClassNotFoundException ignored) {
+            return VersionNumber.UNKNOWN
+        } catch (LinkageError ignored) {
+            return VersionNumber.UNKNOWN
+        }
+    }
+
+    private String chooseBackend(ScalaCompileSpec spec) {
+        // deprecated, but must still honor
+        if (spec.scalaCompileOptions.targetCompatibility) {
+            return VersionNumber.parse(spec.scalaCompileOptions.targetCompatibility)
+        }
+
+        def target = VersionNumber.parse(spec.targetCompatibility)
+        if (target <= VersionNumber.parse("1.5")) { return "jvm-${target.major}.${target.minor}" }
+
+        def scalaVersion = sniffScalaVersion(spec.scalaClasspath)
+        if (scalaVersion >= VersionNumber.parse("2.10.0-AAA")) { return "jvm-${target.major}.${target.minor}" }
+
+        // prior to Scala 2.10, scalac Ant task only supports "jvm-1.5" and "msil" backends
+        return "jvm-1.5"
+    }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DaemonScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DaemonScalaCompiler.java
new file mode 100644
index 0000000..00a35bf
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DaemonScalaCompiler.java
@@ -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.api.internal.tasks.scala;
+
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.daemon.CompileResult;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemon;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonFactory;
+import org.gradle.api.internal.tasks.compile.daemon.DaemonForkOptions;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.ForkOptions;
+import org.gradle.api.tasks.scala.ScalaForkOptions;
+import org.gradle.internal.UncheckedException;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DaemonScalaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<ScalaJavaJointCompileSpec> {
+    private final ProjectInternal project;
+    private final Compiler<ScalaJavaJointCompileSpec> delegate;
+    private final CompilerDaemonFactory daemonFactory;
+
+    public DaemonScalaCompiler(ProjectInternal project, Compiler<ScalaJavaJointCompileSpec> delegate, CompilerDaemonFactory daemonFactory) {
+        this.project = project;
+        this.delegate = delegate;
+        this.daemonFactory = daemonFactory;
+    }
+
+    public WorkResult execute(ScalaJavaJointCompileSpec 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(ScalaJavaJointCompileSpec spec) {
+        return createJavaForkOptions(spec).mergeWith(createScalaForkOptions(spec));
+    }
+
+    private DaemonForkOptions createJavaForkOptions(ScalaJavaJointCompileSpec spec) {
+        ForkOptions options = spec.getCompileOptions().getForkOptions();
+        return new DaemonForkOptions(options.getMemoryInitialSize(), options.getMemoryMaximumSize(), options.getJvmArgs());
+    }
+
+    private DaemonForkOptions createScalaForkOptions(ScalaJavaJointCompileSpec spec) {
+        ScalaForkOptions options = spec.getScalaCompileOptions().getForkOptions();
+        List<String> sharedPackages = Arrays.asList("scala", "com.typesafe.zinc", "xsbti", "com.sun.tools.javac");
+        return new DaemonForkOptions(options.getMemoryInitialSize(), options.getMemoryMaximumSize(),
+                options.getJvmArgs(), spec.getZincClasspath(), sharedPackages);
+    }
+}
+
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
index db504d1..297cf32 100644
--- 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
@@ -20,10 +20,13 @@ import org.gradle.api.internal.tasks.compile.DefaultJvmLanguageCompileSpec;
 import org.gradle.api.tasks.scala.ScalaCompileOptions;
 
 import java.io.File;
+import java.util.Map;
 
 public class DefaultScalaCompileSpec extends DefaultJvmLanguageCompileSpec implements ScalaCompileSpec {
     private final ScalaCompileOptions options = new ScalaCompileOptions();
     private Iterable<File> scalaClasspath;
+    private Iterable<File> zincClasspath;
+    private Map<File, File> analysisMap;
 
     public ScalaCompileOptions getScalaCompileOptions() {
         return options;
@@ -36,4 +39,20 @@ public class DefaultScalaCompileSpec extends DefaultJvmLanguageCompileSpec imple
     public void setScalaClasspath(Iterable<File> scalaClasspath) {
         this.scalaClasspath = scalaClasspath;
     }
+
+    public Iterable<File> getZincClasspath() {
+        return zincClasspath;
+    }
+
+    public void setZincClasspath(Iterable<File> zincClasspath) {
+        this.zincClasspath = zincClasspath;
+    }
+
+    public Map<File, File> getAnalysisMap() {
+        return analysisMap;
+    }
+
+    public void setAnalysisMap(Map<File, File> analysisMap) {
+        this.analysisMap = analysisMap;
+    }
 }
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
index 05f3e47..b0b393e 100644
--- 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
@@ -20,15 +20,22 @@ import org.gradle.api.internal.tasks.compile.DefaultJavaCompileSpec;
 import org.gradle.api.tasks.scala.ScalaCompileOptions;
 
 import java.io.File;
+import java.util.Map;
 
 public class DefaultScalaJavaJointCompileSpec extends DefaultJavaCompileSpec implements ScalaJavaJointCompileSpec {
-    private final ScalaCompileOptions options = new ScalaCompileOptions();
+    private ScalaCompileOptions options;
     private Iterable<File> scalaClasspath;
+    private Iterable<File> zincClasspath;
+    private Map<File, File> analysisMap;
 
     public ScalaCompileOptions getScalaCompileOptions() {
         return options;
     }
 
+    public void setScalaCompileOptions(ScalaCompileOptions options) {
+        this.options = options;
+    }
+
     public Iterable<File> getScalaClasspath() {
         return scalaClasspath;
     }
@@ -36,4 +43,20 @@ public class DefaultScalaJavaJointCompileSpec extends DefaultJavaCompileSpec imp
     public void setScalaClasspath(Iterable<File> scalaClasspath) {
         this.scalaClasspath = scalaClasspath;
     }
+
+    public Iterable<File> getZincClasspath() {
+        return zincClasspath;
+    }
+
+    public void setZincClasspath(Iterable<File> zincClasspath) {
+        this.zincClasspath = zincClasspath;
+    }
+
+    public Map<File, File> getAnalysisMap() {
+        return analysisMap;
+    }
+
+    public void setAnalysisMap(Map<File, File> analysisMap) {
+        this.analysisMap = analysisMap;
+    }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DelegatingScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DelegatingScalaCompiler.java
new file mode 100644
index 0000000..1e1c3f2
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DelegatingScalaCompiler.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.tasks.scala;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+
+public class DelegatingScalaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<ScalaJavaJointCompileSpec> {
+    private final ScalaCompilerFactory compilerFactory;
+
+    public DelegatingScalaCompiler(ScalaCompilerFactory compilerFactory) {
+        this.compilerFactory = compilerFactory;
+    }
+
+    public WorkResult execute(ScalaJavaJointCompileSpec spec) {
+        Compiler<ScalaJavaJointCompileSpec> delegate = compilerFactory.create(spec.getScalaCompileOptions(), spec.getCompileOptions());
+        return delegate.execute(spec);
+    }
+}
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 55dbb7a..43fb239 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,10 +16,8 @@
 package org.gradle.api.internal.tasks.scala;
 
 import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.tasks.compile.*;
 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;
 
 public class IncrementalScalaCompiler extends IncrementalJavaCompilerSupport<ScalaJavaJointCompileSpec>
         implements Compiler<ScalaJavaJointCompileSpec> {
@@ -38,6 +36,9 @@ public class IncrementalScalaCompiler extends IncrementalJavaCompilerSupport<Sca
 
     @Override
     protected StaleClassCleaner createCleaner(ScalaJavaJointCompileSpec spec) {
-        return new SimpleStaleClassCleaner(taskOutputs);
+        if (spec.getScalaCompileOptions().isUseAnt()) {
+            return new SimpleStaleClassCleaner(taskOutputs);
+        }
+        return new NoOpStaleClassCleaner();
     }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompiler.java
new file mode 100644
index 0000000..baf4054
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompiler.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.api.internal.tasks.scala;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.internal.tasks.compile.*;
+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.util.CollectionUtils;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A Scala {@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 NormalizingScalaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<ScalaJavaJointCompileSpec> {
+    private static final Logger LOGGER = Logging.getLogger(NormalizingScalaCompiler.class);
+    private final Compiler<ScalaJavaJointCompileSpec> delegate;
+
+    public NormalizingScalaCompiler(Compiler<ScalaJavaJointCompileSpec> delegate) {
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(ScalaJavaJointCompileSpec spec) {
+        resolveAndFilterSourceFiles(spec);
+        resolveClasspath(spec);
+        resolveNonStringsInCompilerArgs(spec);
+        logSourceFiles(spec);
+        logCompilerArguments(spec);
+        return delegateAndHandleErrors(spec);
+    }
+
+    private void resolveAndFilterSourceFiles(final ScalaJavaJointCompileSpec spec) {
+        spec.setSource(new SimpleFileCollection(spec.getSource().getFiles()));
+    }
+
+    private void resolveClasspath(ScalaJavaJointCompileSpec spec) {
+        spec.setClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getClasspath())));
+        spec.setScalaClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getScalaClasspath())));
+        spec.setZincClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getZincClasspath())));
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Class path: {}", spec.getClasspath());
+            LOGGER.debug("Scala class path: {}", spec.getScalaClasspath());
+            LOGGER.debug("Zinc class path: {}", spec.getZincClasspath());
+        }
+    }
+
+    private void resolveNonStringsInCompilerArgs(ScalaJavaJointCompileSpec spec) {
+        // in particular, this is about GStrings
+        spec.getCompileOptions().setCompilerArgs(CollectionUtils.toStringList(spec.getCompileOptions().getCompilerArgs()));
+    }
+
+    private void logSourceFiles(ScalaJavaJointCompileSpec spec) {
+        if (!spec.getScalaCompileOptions().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(ScalaJavaJointCompileSpec 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(ScalaJavaJointCompileSpec spec) {
+        try {
+            return delegate.execute(spec);
+        } catch (CompilationFailedException e) {
+            if (spec.getScalaCompileOptions().isFailOnError()) {
+                throw e;
+            }
+            LOGGER.debug("Ignoring compilation failure.");
+            return new SimpleWorkResult(false);
+        }
+    }
+}
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
index 346bd7f..1d36e7f 100644
--- 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
@@ -20,6 +20,7 @@ import org.gradle.api.internal.tasks.compile.JvmLanguageCompileSpec;
 import org.gradle.api.tasks.scala.ScalaCompileOptions;
 
 import java.io.File;
+import java.util.Map;
 
 public interface ScalaCompileSpec extends JvmLanguageCompileSpec {
     ScalaCompileOptions getScalaCompileOptions();
@@ -27,4 +28,12 @@ public interface ScalaCompileSpec extends JvmLanguageCompileSpec {
     Iterable<File> getScalaClasspath();
 
     void setScalaClasspath(Iterable<File> classpath);
+
+    Iterable<File> getZincClasspath();
+
+    void setZincClasspath(Iterable<File> classpath);
+
+    Map<File, File> getAnalysisMap();
+
+    void setAnalysisMap(Map<File, File> analysisMap);
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGenerator.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGenerator.java
new file mode 100644
index 0000000..818f8a4
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGenerator.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.api.internal.tasks.scala;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.util.List;
+
+public class ScalaCompilerArgumentsGenerator {
+    public List<String> generate(ScalaCompileSpec spec) {
+        List<String> result = Lists.newArrayList();
+
+        ScalaCompileOptions options = spec.getScalaCompileOptions();
+        addFlag("-deprecation", options.isDeprecation(), result);
+        addFlag("-unchecked", options.isUnchecked(), result);
+        addConcatenatedOption("-g:", options.getDebugLevel(), result);
+        addFlag("-optimise", options.isOptimize(), result);
+        addOption("-encoding", options.getEncoding(), result);
+        addFlag("-verbose", "verbose".equals(options.getDebugLevel()), result);
+        addFlag("-Ydebug", "debug".equals(options.getDebugLevel()), result);
+        if (options.getLoggingPhases() != null) {
+            for (String phase : options.getLoggingPhases()) {
+                addConcatenatedOption("-Ylog:", phase, result);
+            }
+        }
+        if (options.getAdditionalParameters() != null) {
+            result.addAll(options.getAdditionalParameters());
+        }
+
+        return result;
+    }
+
+    private void addFlag(String name, boolean value, List<String> result) {
+        if (value) {
+            result.add(name);
+        }
+    }
+
+    private void addOption(String name, Object value, List<String> result) {
+        if (value != null) {
+            result.add(name);
+            result.add(value.toString());
+        }
+    }
+
+    private void addConcatenatedOption(String name, Object value, List<String> result) {
+        if (value != null) {
+            result.add(name + value.toString());
+        }
+    }
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerFactory.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerFactory.java
new file mode 100644
index 0000000..3dbbef6
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerFactory.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.tasks.scala;
+
+import org.gradle.api.AntBuilder;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.AntJavaCompiler;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonFactory;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+import org.gradle.internal.Factory;
+
+public class ScalaCompilerFactory {
+    private final ProjectInternal project;
+    private final IsolatedAntBuilder antBuilder;
+    private final Factory<AntBuilder> antBuilderFactory;
+
+    public ScalaCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, Factory<AntBuilder> antBuilderFactory) {
+        this.project = project;
+        this.antBuilder = antBuilder;
+        this.antBuilderFactory = antBuilderFactory;
+    }
+
+    public org.gradle.api.internal.tasks.compile.Compiler<ScalaJavaJointCompileSpec> create(ScalaCompileOptions scalaOptions, CompileOptions javaOptions) {
+        if (scalaOptions.isUseAnt()) {
+            Compiler<ScalaCompileSpec> scalaCompiler = new AntScalaCompiler(antBuilder);
+            Compiler<JavaCompileSpec> javaCompiler = new AntJavaCompiler(antBuilderFactory);
+            return new DefaultScalaJavaJointCompiler(scalaCompiler, javaCompiler);
+        }
+
+        if (!scalaOptions.isFork()) {
+            throw new GradleException("The Zinc based Scala compiler ('scalaCompileOptions.useAnt=false') "
+                    + "requires forking ('scalaCompileOptions.fork=true'), but the latter is set to 'false'.");
+        }
+
+        // currently, we leave it to ZincScalaCompiler to also compile the Java code
+        Compiler<ScalaJavaJointCompileSpec> scalaCompiler;
+        try {
+            scalaCompiler = (Compiler<ScalaJavaJointCompileSpec>) getClass().getClassLoader()
+                    .loadClass("org.gradle.api.internal.tasks.scala.jdk6.ZincScalaCompiler").newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException("Internal error: Failed to load org.gradle.api.internal.tasks.scala.jdk6.ZincScalaCompiler", e);
+        }
+
+        CompilerDaemonFactory daemonFactory = CompilerDaemonManager.getInstance();
+        scalaCompiler = new DaemonScalaCompiler(project, scalaCompiler, daemonFactory);
+        return new NormalizingScalaCompiler(scalaCompiler);
+    }
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java
new file mode 100644
index 0000000..85d47e3
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.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.tasks.scala.jdk6;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.typesafe.zinc.Inputs;
+import com.typesafe.zinc.SbtJars;
+import com.typesafe.zinc.ScalaLocation;
+import com.typesafe.zinc.Setup;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.tasks.compile.CompilationFailedException;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompilerArgumentsBuilder;
+import org.gradle.api.internal.tasks.compile.SimpleWorkResult;
+import org.gradle.api.internal.tasks.scala.ScalaCompilerArgumentsGenerator;
+import org.gradle.api.internal.tasks.scala.ScalaJavaJointCompileSpec;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.jvm.Jvm;
+import xsbti.F0;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+
+public class ZincScalaCompiler implements Compiler<ScalaJavaJointCompileSpec>, Serializable {
+    private static final Logger LOGGER = Logging.getLogger(ZincScalaCompiler.class);
+
+    public WorkResult execute(ScalaJavaJointCompileSpec spec) {
+        if (!JavaVersion.current().isJava6Compatible()) {
+            throw new GradleException("To use the Zinc Scala compiler, Java 6 or higher is required.");
+        }
+        return Compiler.execute(spec);
+    }
+
+    // need to defer loading of Zinc/sbt/Scala classes until we are
+    // running in the compiler daemon and have them on the class path
+    private static class Compiler {
+        static WorkResult execute(ScalaJavaJointCompileSpec spec) {
+            LOGGER.info("Compiling with Zinc Scala compiler.");
+
+            xsbti.Logger logger = new SbtLoggerAdapter();
+
+            com.typesafe.zinc.Compiler compiler = createCompiler(spec.getScalaClasspath(), spec.getZincClasspath(), logger);
+            List<String> scalacOptions = new ScalaCompilerArgumentsGenerator().generate(spec);
+            List<String> javacOptions = new JavaCompilerArgumentsBuilder(spec).includeClasspath(false).build();
+            Inputs inputs = Inputs.create(ImmutableList.copyOf(spec.getClasspath()), ImmutableList.copyOf(spec.getSource()), spec.getDestinationDir(),
+                    scalacOptions, javacOptions, spec.getScalaCompileOptions().getIncrementalOptions().getAnalysisFile(), spec.getAnalysisMap(), "mixed");
+            if (LOGGER.isDebugEnabled()) {
+                Inputs.debug(inputs, logger);
+            }
+
+            try {
+                compiler.compile(inputs, logger);
+            } catch (xsbti.CompileFailed e) {
+                throw new CompilationFailedException(e);
+            }
+
+            return new SimpleWorkResult(true);
+        }
+
+        static com.typesafe.zinc.Compiler createCompiler(Iterable<File> scalaClasspath, Iterable<File> zincClasspath, xsbti.Logger logger) {
+            ScalaLocation scalaLocation = ScalaLocation.fromPath(Lists.newArrayList(scalaClasspath));
+            SbtJars sbtJars = SbtJars.fromPath(Lists.newArrayList(zincClasspath));
+            Setup setup = Setup.create(scalaLocation, sbtJars, Jvm.current().getJavaHome());
+            if (LOGGER.isDebugEnabled()) {
+                Setup.debug(setup, logger);
+            }
+            return com.typesafe.zinc.Compiler.getOrCreate(setup, logger);
+        }
+    }
+
+    private static class SbtLoggerAdapter implements xsbti.Logger {
+        public void error(F0<String> msg) {
+            LOGGER.error(msg.apply());
+        }
+
+        public void warn(F0<String> msg) {
+            LOGGER.warn(msg.apply());
+        }
+
+        public void info(F0<String> msg) {
+            LOGGER.info(msg.apply());
+        }
+
+        public void debug(F0<String> msg) {
+            LOGGER.debug(msg.apply());
+        }
+
+        public void trace(F0<Throwable> exception) {
+            LOGGER.trace(exception.apply().toString());
+        }
+    }
+}
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 a51a621..929a270 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
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.plugins.scala;
 
-
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.file.FileTreeElement
@@ -26,44 +25,89 @@ 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
+import org.gradle.api.tasks.JavaExec
 
-public class ScalaBasePlugin implements Plugin<Project> {
+class ScalaBasePlugin implements Plugin<Project> {
     // public configurations
-    public static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools";
+    static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools"
+    static final String ZINC_CONFIGURATION_NAME = "zinc"
 
-    public void apply(Project project) {
-        JavaBasePlugin javaPlugin = project.plugins.apply(JavaBasePlugin.class);
+    void apply(Project project) {
+        def javaPlugin = project.plugins.apply(JavaBasePlugin.class)
 
-        project.configurations.add(SCALA_TOOLS_CONFIGURATION_NAME).setVisible(false).setTransitive(true).
-                setDescription("The Scala tools libraries to be used for this Scala project.");
+        project.configurations.add(SCALA_TOOLS_CONFIGURATION_NAME)
+                .setVisible(false)
+                .setDescription("The Scala tools libraries to be used for this Scala project.")
+        project.configurations.add(ZINC_CONFIGURATION_NAME)
+                .setVisible(false)
+                .setDescription("The Zinc incremental compiler to be used for this Scala project.")
 
         configureCompileDefaults(project, javaPlugin)
         configureSourceSetDefaults(project, javaPlugin)
-        configureScaladoc(project);
+        configureScaladoc(project)
     }
 
     private void configureSourceSetDefaults(Project project, JavaBasePlugin javaPlugin) {
-        project.convention.getPlugin(JavaPluginConvention.class).sourceSets.all {SourceSet sourceSet ->
+        project.convention.getPlugin(JavaPluginConvention.class).sourceSets.all { SourceSet sourceSet ->
             sourceSet.convention.plugins.scala = new DefaultScalaSourceSet(sourceSet.displayName, project.fileResolver)
             sourceSet.scala.srcDir { project.file("src/$sourceSet.name/scala")}
             sourceSet.allJava.source(sourceSet.scala)
             sourceSet.allSource.source(sourceSet.scala)
             sourceSet.resources.filter.exclude { FileTreeElement element -> sourceSet.scala.contains(element.file) }
 
-            String taskName = sourceSet.getCompileTaskName('scala')
-            ScalaCompile scalaCompile = project.tasks.add(taskName, ScalaCompile.class);
-            scalaCompile.dependsOn sourceSet.compileJavaTaskName
-            javaPlugin.configureForSourceSet(sourceSet, scalaCompile);
-            scalaCompile.description = "Compiles the $sourceSet.scala.";
-            scalaCompile.source = sourceSet.scala
+            configureScalaCompile(project, javaPlugin, sourceSet)
+            configureScalaConsole(project, sourceSet)
+        }
+    }
+
+    private void configureScalaCompile(Project project, JavaBasePlugin javaPlugin, SourceSet sourceSet) {
+        def taskName = sourceSet.getCompileTaskName('scala')
+        def scalaCompile = project.tasks.add(taskName, ScalaCompile)
+        scalaCompile.dependsOn sourceSet.compileJavaTaskName
+        javaPlugin.configureForSourceSet(sourceSet, scalaCompile)
+        scalaCompile.description = "Compiles the $sourceSet.scala."
+        scalaCompile.source = sourceSet.scala
+        project.tasks[sourceSet.classesTaskName].dependsOn(taskName)
 
-            project.tasks[sourceSet.classesTaskName].dependsOn(taskName)
+        // cannot use convention mapping because the resulting object won't be serializable
+        // cannot compute at task execution time because we need association with source set
+        project.gradle.projectsEvaluated {
+            scalaCompile.scalaCompileOptions.incrementalOptions.with {
+                if (!analysisFile) {
+                    analysisFile = new File("$project.buildDir/tmp/scala/compilerAnalysis/${scalaCompile.name}.analysis")
+                }
+                if (!publishedCode) {
+                    def jarTask = project.tasks.findByName(sourceSet.getJarTaskName())
+                    publishedCode = jarTask?.archivePath
+                }
+            }
         }
     }
 
+    private void configureScalaConsole(Project project, SourceSet sourceSet) {
+        def taskName = sourceSet.getTaskName("scala", "Console")
+        def scalaConsole = project.tasks.add(taskName, JavaExec)
+        scalaConsole.dependsOn(sourceSet.runtimeClasspath)
+        scalaConsole.description = "Starts a Scala REPL with the $sourceSet.name runtime class path."
+        scalaConsole.main = "scala.tools.nsc.MainGenericRunner"
+        scalaConsole.classpath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        scalaConsole.systemProperty("scala.usejavacp", true)
+        scalaConsole.standardInput = System.in
+        scalaConsole.conventionMapping.jvmArgs = { ["-classpath", sourceSet.runtimeClasspath.asPath] }
+    }
+
     private void configureCompileDefaults(final Project project, JavaBasePlugin javaPlugin) {
-        project.tasks.withType(ScalaCompile.class) {ScalaCompile compile ->
+        project.tasks.withType(ScalaCompile.class) { ScalaCompile compile ->
             compile.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+            compile.conventionMapping.zincClasspath = {
+                def config = project.configurations[ZINC_CONFIGURATION_NAME]
+                if (!compile.scalaCompileOptions.useAnt && config.dependencies.empty) {
+                    project.dependencies {
+                        zinc("com.typesafe.zinc:zinc:0.2.0")
+                    }
+                }
+                config
+            }
         }
     }
 
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/IncrementalCompileOptions.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/IncrementalCompileOptions.java
new file mode 100644
index 0000000..2d2c285
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/IncrementalCompileOptions.java
@@ -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.api.tasks.scala;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.tasks.Input;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Options for incremental compilation of Scala code. Only used if
+ * {@link org.gradle.api.tasks.scala.ScalaCompileOptions#isUseAnt()} is {@code false}.
+ */
+ at Incubating
+public class IncrementalCompileOptions implements Serializable {
+    private static final long serialVersionUID = 0;
+
+    private File analysisFile;
+    private File publishedCode;
+
+    /**
+     * Returns the file path where results of code analysis are to be stored.
+     *
+     * @return the file path where which results of code analysis are to be stored
+     */
+    @Input
+    public File getAnalysisFile() {
+        return analysisFile;
+    }
+
+    /**
+     * Sets the file path where results of code analysis are to be stored.
+     */
+    public void setAnalysisFile(File analysisFile) {
+        this.analysisFile = analysisFile;
+    }
+
+    /**
+     * Returns the directory or archive path by which the code produced by this task
+     * is published to other {@code ScalaCompile} tasks.
+     *
+     * @return the directory or archive path by which the code produced by this task
+     * is published to other {@code ScalaCompile} tasks
+     */
+    // only an input for other task instances
+    public File getPublishedCode() {
+        return publishedCode;
+    }
+
+    /**
+     * Sets the directory or archive path by which the code produced by this task
+     * is published to other {@code ScalaCompile} tasks.
+     */
+    public void setPublishedCode(File publishedCode) {
+        this.publishedCode = publishedCode;
+    }
+}
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 b24d961..c61952d 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
@@ -15,31 +15,53 @@
  */
 package org.gradle.api.tasks.scala;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
 import org.gradle.api.AntBuilder;
+import org.gradle.api.Project;
 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.JavaCompileSpec;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.Compiler;
 import org.gradle.api.internal.tasks.scala.*;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
 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;
+import org.gradle.internal.Factory;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Compiles Scala source files, and optionally, Java source files.
  */
 public class ScalaCompile extends AbstractCompile {
+    private static final Logger LOGGER = Logging.getLogger(ScalaCompile.class);
+
     private FileCollection scalaClasspath;
+    private FileCollection zincClasspath;
     private Compiler<ScalaJavaJointCompileSpec> compiler;
-    private final ScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec();
+    private final CompileOptions compileOptions = new CompileOptions();
+    private final ScalaCompileOptions scalaCompileOptions = new ScalaCompileOptions();
 
+    @Inject
     public ScalaCompile() {
-        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());
+        ProjectInternal projectInternal = (ProjectInternal) getProject();
+        IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
+        Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
+        ScalaCompilerFactory scalaCompilerFactory = new ScalaCompilerFactory(projectInternal, antBuilder, antBuilderFactory);
+        Compiler<ScalaJavaJointCompileSpec> delegatingCompiler = new DelegatingScalaCompiler(scalaCompilerFactory);
+        compiler = new IncrementalScalaCompiler(delegatingCompiler, getOutputs());
     }
 
     /**
@@ -54,12 +76,17 @@ public class ScalaCompile extends AbstractCompile {
         this.scalaClasspath = scalaClasspath;
     }
 
-    public Compiler<ScalaJavaJointCompileSpec> getCompiler() {
-        return compiler;
+    /**
+     * Returns the classpath to use to load the Zinc incremental compiler.
+     * This compiler in turn loads the Scala compiler.
+     */
+    @InputFiles
+    public FileCollection getZincClasspath() {
+        return zincClasspath;
     }
 
-    public void setCompiler(Compiler<ScalaJavaJointCompileSpec> compiler) {
-        this.compiler = compiler;
+    public void setZincClasspath(FileCollection zincClasspath) {
+        this.zincClasspath = zincClasspath;
     }
 
     /**
@@ -67,7 +94,7 @@ public class ScalaCompile extends AbstractCompile {
      */
     @Nested
     public ScalaCompileOptions getScalaCompileOptions() {
-        return spec.getScalaCompileOptions();
+        return scalaCompileOptions;
     }
 
     /**
@@ -75,18 +102,75 @@ public class ScalaCompile extends AbstractCompile {
      */
     @Nested
     public CompileOptions getOptions() {
-        return spec.getCompileOptions();
+        return compileOptions;
+    }
+
+    /**
+     * For testing only.
+     */
+    void setCompiler(Compiler<ScalaJavaJointCompileSpec> compiler) {
+        this.compiler = compiler;
     }
 
     @Override
     protected void compile() {
-        FileTree source = getSource();
-        spec.setSource(source);
+        DefaultScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec();
+        spec.setSource(getSource());
         spec.setDestinationDir(getDestinationDir());
         spec.setClasspath(getClasspath());
         spec.setScalaClasspath(getScalaClasspath());
+        spec.setZincClasspath(getZincClasspath());
         spec.setSourceCompatibility(getSourceCompatibility());
         spec.setTargetCompatibility(getTargetCompatibility());
+        spec.setCompileOptions(compileOptions);
+        spec.setScalaCompileOptions(scalaCompileOptions);
+        if (!scalaCompileOptions.isUseAnt()) {
+            configureIncrementalCompilation(spec);
+        }
+
         compiler.execute(spec);
     }
+
+    private void configureIncrementalCompilation(ScalaCompileSpec spec) {
+        Map<File, File> globalAnalysisMap = getOrCreateGlobalAnalysisMap();
+        HashMap<File, File> filteredMap = filterForClasspath(globalAnalysisMap, spec.getClasspath());
+        spec.setAnalysisMap(filteredMap);
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Analysis file: {}", scalaCompileOptions.getIncrementalOptions().getAnalysisFile());
+            LOGGER.debug("Published code: {}", scalaCompileOptions.getIncrementalOptions().getPublishedCode());
+            LOGGER.debug("Analysis map: {}", filteredMap);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<File, File> getOrCreateGlobalAnalysisMap() {
+        ExtraPropertiesExtension extraProperties = getProject().getRootProject().getExtensions().getExtraProperties();
+        Map<File, File> analysisMap;
+
+        if (extraProperties.has("scalaCompileAnalysisMap")) {
+            analysisMap = (Map) extraProperties.get("scalaCompileAnalysisMap");
+        } else {
+            analysisMap = Maps.newHashMap();
+            for (Project project : getProject().getRootProject().getAllprojects()) {
+                for (ScalaCompile task : project.getTasks().withType(ScalaCompile.class)) {
+                    if (task.getScalaCompileOptions().isUseAnt()) { continue; }
+                    File publishedCode = task.getScalaCompileOptions().getIncrementalOptions().getPublishedCode();
+                    File analysisFile = task.getScalaCompileOptions().getIncrementalOptions().getAnalysisFile();
+                    analysisMap.put(publishedCode, analysisFile);
+                }
+            }
+            extraProperties.set("scalaCompileAnalysisMap", Collections.unmodifiableMap(analysisMap));
+        }
+        return analysisMap;
+    }
+
+    private HashMap<File, File> filterForClasspath(Map<File, File> analysisMap, Iterable<File> classpath) {
+        final Set<File> classpathLookup = Sets.newHashSet(classpath);
+        return Maps.newHashMap(Maps.filterEntries(analysisMap, new Predicate<Map.Entry<File, File>>() {
+            public boolean apply(Map.Entry<File, File> entry) {
+                return classpathLookup.contains(entry.getKey());
+            }
+        }));
+    }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy
deleted file mode 100644
index 5e8d216..0000000
--- a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy
+++ /dev/null
@@ -1,146 +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.scala
-
-import org.gradle.api.tasks.compile.AbstractOptions
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional
-
-public class ScalaCompileOptions extends AbstractOptions {
-
-    /**
-     * Whether to use the fsc compile daemon.
-     */
-    boolean useCompileDaemon = false
-
-    // NOTE: Does not work for scalac 2.7.1 due to a bug in the ant task
-    /**
-     * Server (host:port) on which the compile daemon is running.
-     * The host must share disk access with the client process.
-     * If not specified, launches the daemon on the localhost.
-     * This parameter can only be specified if useCompileDaemon is true.
-     */
-    String daemonServer;
-
-    /**
-     * Fail the build on compilation errors.
-     */
-    boolean failOnError = true
-
-    /**
-     * Generate deprecation information.
-     */
-    boolean deprecation = true
-
-    /**
-     * Generate unchecked information.
-     */
-    boolean unchecked = true
-
-    /**
-     * Generate debugging information.
-     * Legal values: none, source, line, vars, notailcalls
-     */
-    @Input @Optional
-    String debugLevel
-
-    /**
-     * Run optimisations.
-     */
-    @Input
-    boolean optimize = false
-
-    /**
-     * Encoding of source files.
-     */
-    @Input @Optional
-    String encoding = null
-
-    /**
-     * Whether to force the compilation of all files.
-     * Legal values:
-     * - never (only compile modified files)
-     * - changed (compile all files when at least one file is modified)
-     * - always (always recompile all files)
-     */
-    String force = "never"
-
-    /**
-     * Specifies which backend to use.
-     * Legal values: 1.4, 1.5
-     */
-    @Input
-    String targetCompatibility = '1.5'
-
-    /**
-     * Additional parameters passed to the compiler.
-     * Each parameter must start with '-'.
-     */
-    List<String> additionalParameters
-
-    /**
-     * List files to be compiled.
-     */
-    boolean listFiles
-
-    /**
-     * Specifies the amount of logging.
-     * Legal values:  none, verbose, debug
-     */
-    String loggingLevel
-
-    /**
-     * Phases of the compiler to log.
-     * Legal values: namer, typer, pickler, uncurry, tailcalls, transmatch, explicitouter, erasure,
-     *               lambdalift, flatten, constructors, mixin, icode, jvm, terminal.
-     */
-    List<String> loggingPhases
-
-
-    Map fieldName2AntMap() {
-        [
-                failOnError: 'failonerror',
-                loggingLevel: 'logging',
-                loggingPhases: 'logPhase',
-                targetCompatibility: 'target',
-                optimize: 'optimise',
-                daemonServer: 'server',
-                listFiles: 'scalacDebugging',
-                debugLevel: 'debugInfo',
-                additionalParameters: 'addParams'
-        ]
-    }
-
-    Map fieldValue2AntMap() {
-        [
-                deprecation: {toOnOffString(deprecation)},
-                unchecked: {toOnOffString(unchecked)},
-                optimize: {toOnOffString(optimize)},
-                targetCompatibility: {"jvm-${targetCompatibility}"},
-                loggingPhases: {loggingPhases.isEmpty() ? ' ' : loggingPhases.join(',')},
-                additionalParameters: {additionalParameters.isEmpty() ? ' ' : additionalParameters.join(' ')},
-        ]
-    }
-
-    List excludedFieldsFromOptionMap() {
-        ['useCompileDaemon'] + (optimize ? [] : ['optimize'])
-    }
-
-    private String toOnOffString(value) {
-        return value ? 'on' : 'off'
-    }
-
-}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.java
new file mode 100644
index 0000000..66b92fb
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.scala;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.compile.AbstractOptions;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import java.util.List;
+
+/**
+ * Options for Scala compilation.
+ */
+public class ScalaCompileOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    private static final ImmutableMap<String, String> FIELD_NAMES_TO_ANT_PROPERTIES = new ImmutableMap.Builder<String, String>()
+            .put("loggingLevel", "logging")
+            .put("loggingPhases", "logphase")
+            .put("targetCompatibility", "target")
+            .put("optimize", "optimise")
+            .put("daemonServer", "server")
+            .put("listFiles", "scalacdebugging")
+            .put("debugLevel", "debuginfo")
+            .put("additionalParameters", "addparams")
+            .build();
+
+    private boolean useCompileDaemon;
+
+    private String daemonServer;
+
+    private boolean failOnError = true;
+
+    private boolean deprecation = true;
+
+    private boolean unchecked = true;
+
+    private String debugLevel;
+
+    private boolean optimize;
+
+    private String encoding;
+
+    private String force = "never";
+
+    private String targetCompatibility;
+
+    private List<String> additionalParameters;
+
+    private boolean listFiles;
+
+    private String loggingLevel;
+
+    private List<String> loggingPhases;
+
+    private boolean fork;
+
+    private ScalaForkOptions forkOptions = new ScalaForkOptions();
+
+    private boolean useAnt = true;
+
+    private IncrementalCompileOptions incrementalOptions = new IncrementalCompileOptions();
+
+    /**
+     * Whether to use the fsc compile daemon.
+     */
+    public boolean isUseCompileDaemon() {
+        return useCompileDaemon;
+    }
+
+    public void setUseCompileDaemon(boolean useCompileDaemon) {
+        this.useCompileDaemon = useCompileDaemon;
+    }
+
+    // NOTE: Does not work for scalac 2.7.1 due to a bug in the Ant task
+    /**
+     * Server (host:port) on which the compile daemon is running.
+     * The host must share disk access with the client process.
+     * If not specified, launches the daemon on the localhost.
+     * This parameter can only be specified if useCompileDaemon is true.
+     */
+    public String getDaemonServer() {
+        return daemonServer;
+    }
+
+    public void setDaemonServer(String daemonServer) {
+        this.daemonServer = daemonServer;
+    }
+
+    /**
+     * Fail the build on compilation errors.
+     */
+    public boolean isFailOnError() {
+        return failOnError;
+    }
+
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    /**
+     * Generate deprecation information.
+     */
+    public boolean isDeprecation() {
+        return deprecation;
+    }
+
+    public void setDeprecation(boolean deprecation) {
+        this.deprecation = deprecation;
+    }
+
+    /**
+     * Generate unchecked information.
+     */
+    public boolean isUnchecked() {
+        return unchecked;
+    }
+
+    public void setUnchecked(boolean unchecked) {
+        this.unchecked = unchecked;
+    }
+
+    /**
+     * Generate debugging information.
+     * Legal values: none, source, line, vars, notailcalls
+     */
+    @Input @Optional
+    public String getDebugLevel() {
+        return debugLevel;
+    }
+
+    public void setDebugLevel(String debugLevel) {
+        this.debugLevel = debugLevel;
+    }
+
+    /**
+     * Run optimizations.
+     */
+    @Input
+    public boolean isOptimize() {
+        return optimize;
+    }
+
+    public void setOptimize(boolean optimize) {
+        this.optimize = optimize;
+    }
+
+    /**
+     * Encoding of source files.
+     */
+    @Input @Optional
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Whether to force the compilation of all files.
+     * Legal values:
+     * - never (only compile modified files)
+     * - changed (compile all files when at least one file is modified)
+     * - always (always recompile all files)
+     */
+    public String getForce() {
+        return force;
+    }
+
+    public void setForce(String force) {
+        this.force = force;
+    }
+
+    /**
+     * Returns which backend is to be used.
+     *
+     * @deprecated use {@link ScalaCompile#getTargetCompatibility} instead
+     */
+    @Input
+    @Optional
+    @Deprecated
+    public String getTargetCompatibility() {
+        return targetCompatibility;
+    }
+
+    /**
+     * Sets which backend is to be used.
+     *
+     * @deprecated use {@link ScalaCompile#setTargetCompatibility} instead
+     */
+    @Deprecated
+    public void setTargetCompatibility(String targetCompatibility) {
+        this.targetCompatibility = targetCompatibility;
+    }
+
+    /**
+     * Additional parameters passed to the compiler.
+     * Each parameter must start with '-'.
+     */
+    public List<String> getAdditionalParameters() {
+        return additionalParameters;
+    }
+
+    public void setAdditionalParameters(List<String> additionalParameters) {
+        this.additionalParameters = additionalParameters;
+    }
+
+    /**
+     * List files to be compiled.
+     */
+    public boolean isListFiles() {
+        return listFiles;
+    }
+
+    public void setListFiles(boolean listFiles) {
+        this.listFiles = listFiles;
+    }
+
+    /**
+     * Specifies the amount of logging.
+     * Legal values:  none, verbose, debug
+     */
+    public String getLoggingLevel() {
+        return loggingLevel;
+    }
+
+    public void setLoggingLevel(String loggingLevel) {
+        this.loggingLevel = loggingLevel;
+    }
+
+    /**
+     * Phases of the compiler to log.
+     * Legal values: namer, typer, pickler, uncurry, tailcalls, transmatch, explicitouter, erasure,
+     *               lambdalift, flatten, constructors, mixin, icode, jvm, terminal.
+     */
+    public List<String> getLoggingPhases() {
+        return loggingPhases;
+    }
+
+    public void setLoggingPhases(List<String> loggingPhases) {
+        this.loggingPhases = loggingPhases;
+    }
+
+    /**
+     * Whether to run the Scala compiler in a separate process. Defaults to {@code false}
+     * for the Ant based compiler ({@code useAnt = true}), and to {@code true} for the Zinc
+     * based compiler ({@code useAnt = false}).
+     */
+    public boolean isFork() {
+        return fork;
+    }
+
+    public void setFork(boolean fork) {
+        this.fork = fork;
+    }
+
+    /**
+     * Options for running the Scala compiler in a separate process. These options only take effect
+     * if {@code fork} is set to {@code true}.
+     */
+    public ScalaForkOptions getForkOptions() {
+        return forkOptions;
+    }
+
+    public void setForkOptions(ScalaForkOptions forkOptions) {
+        this.forkOptions = forkOptions;
+    }
+
+    /**
+     * Tells whether to use Ant for compilation. If {@code true}, the standard Ant scalac (or fsc) task will be used for
+     * Scala and Java joint compilation. If {@code false}, the Zinc incremental compiler will be used
+     * instead. The latter can be significantly faster, especially if there are few source code changes
+     * between compiler runs. Defaults to {@code true}.
+     */
+    public boolean isUseAnt() {
+        return useAnt;
+    }
+
+    public void setUseAnt(boolean useAnt) {
+        this.useAnt = useAnt;
+        if (!useAnt) {
+            setFork(true);
+        }
+    }
+
+    @Nested
+    public IncrementalCompileOptions getIncrementalOptions() {
+        return incrementalOptions;
+    }
+
+    public void setIncrementalOptions(IncrementalCompileOptions incrementalOptions) {
+        this.incrementalOptions = incrementalOptions;
+    }
+
+    protected boolean excludeFromAntProperties(String fieldName) {
+        return fieldName.equals("useCompileDaemon")
+                || fieldName.equals("forkOptions")
+                || fieldName.equals("useAnt")
+                || fieldName.equals("incrementalOptions")
+                || fieldName.equals("targetCompatibility") // handled directly by AntScalaCompiler
+                || fieldName.equals("optimize") && !optimize;
+    }
+
+    protected String getAntPropertyName(String fieldName) {
+        if (FIELD_NAMES_TO_ANT_PROPERTIES.containsKey(fieldName)) {
+            return FIELD_NAMES_TO_ANT_PROPERTIES.get(fieldName);
+        }
+        return fieldName;
+    }
+
+    protected Object getAntPropertyValue(String fieldName, Object value) {
+        if (fieldName.equals("deprecation")) {
+            return toOnOffString(isDeprecation());
+        }
+        if (fieldName.equals("unchecked")) {
+            return toOnOffString(isUnchecked());
+        }
+        if (fieldName.equals("optimize")) {
+            return toOnOffString(isOptimize());
+        }
+        if (fieldName.equals("targetCompatibility")) {
+            return String.format("jvm-%s", getTargetCompatibility());
+        }
+        if (fieldName.equals("loggingPhases")) {
+            return getLoggingPhases().isEmpty() ? " " : Joiner.on(',').join(getLoggingPhases());
+        }
+        if (fieldName.equals("additionalParameters")) {
+            return getAdditionalParameters().isEmpty() ? " " : Joiner.on(' ').join(getAdditionalParameters());
+        }
+        return value;
+    }
+
+    private String toOnOffString(boolean flag) {
+        return flag ? "on" : "off";
+    }
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy
index 29ccdbd..da74b70 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy
@@ -83,19 +83,26 @@ public class ScalaDocOptions extends AbstractOptions {
     @Input @Optional
     List<String> additionalParameters
 
-
-    Map fieldName2AntMap() {
-        [
-                additionalParameters: 'addParams'
-        ]
+    @Override
+    protected String getAntPropertyName(String fieldName) {
+        if (fieldName == "additionalParameters") {
+            return "addParams"
+        }
+        return fieldName
     }
 
-    Map fieldValue2AntMap() {
-        [
-                deprecation: {toOnOffString(deprecation)},
-                unchecked: {toOnOffString(unchecked)},
-                additionalParameters: {additionalParameters.isEmpty() ? ' ' : additionalParameters.join(' ')},
-        ]
+    @Override
+    protected Object getAntPropertyValue(String fieldName, Object value) {
+        if (fieldName == "deprecation") {
+            return toOnOffString(deprecation)
+        }
+        if (fieldName == "unchecked") {
+            return toOnOffString(unchecked)
+        }
+        if (fieldName == "additionalParameters") {
+            return additionalParameters.isEmpty() ? ' ' : additionalParameters.join(' ')
+        }
+        return value
     }
 
     private String toOnOffString(value) {
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaForkOptions.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaForkOptions.java
new file mode 100644
index 0000000..c5ede40
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaForkOptions.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.tasks.scala;
+
+/*
+ * 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 org.gradle.api.tasks.compile.BaseForkOptions;
+
+/**
+ * Fork options for Scala compilation. Only take effect if {@code ScalaCompileOptions.fork}
+ * is {@code true}.
+ */
+public class ScalaForkOptions extends BaseForkOptions {
+    private static final long serialVersionUID = 0;
+}
\ No newline at end of file
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompilerTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompilerTest.groovy
new file mode 100644
index 0000000..2a2b2c0
--- /dev/null
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/NormalizingScalaCompilerTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.tasks.WorkResult
+import org.gradle.api.tasks.compile.CompileOptions
+import org.gradle.api.tasks.scala.ScalaCompileOptions
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.internal.tasks.compile.CompilationFailedException
+import org.gradle.api.internal.tasks.compile.Compiler
+
+import groovy.transform.InheritConstructors
+
+import spock.lang.Specification
+
+class NormalizingScalaCompilerTest extends Specification {
+    Compiler<ScalaJavaJointCompileSpec> target = Mock()
+    DefaultScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec()
+    NormalizingScalaCompiler compiler = new NormalizingScalaCompiler(target)
+
+    def setup() {
+        spec.source = files("Source1.java", "Source2.java", "Source3.java")
+        spec.classpath = files("Dep1.jar", "Dep2.jar", "Dep3.jar")
+        spec.scalaClasspath = files("scala-compiler.jar", "scala-library.jar")
+        spec.zincClasspath = files("zinc.jar", "zinc-dep.jar")
+        spec.compileOptions = new CompileOptions()
+        spec.scalaCompileOptions = new ScalaCompileOptions()
+    }
+
+    def "delegates to target compiler after resolving source and classpaths"() {
+        WorkResult workResult = Mock()
+
+        when:
+        def result = compiler.execute(spec)
+
+        then:
+        1 * target.execute(spec) >> {
+            assert spec.source.getClass() == SimpleFileCollection
+            assert spec.source as List == old(spec.source as List)
+
+            assert spec.classpath.getClass() == SimpleFileCollection
+            assert spec.classpath as List == old(spec.classpath as List)
+
+            assert spec.scalaClasspath.getClass() == SimpleFileCollection
+            assert spec.scalaClasspath as List == old(spec.scalaClasspath as List)
+
+            assert spec.zincClasspath.getClass() == SimpleFileCollection
+            assert spec.zincClasspath as List == old(spec.zincClasspath as List)
+
+            workResult
+        }
+        result == workResult
+    }
+
+    def "propagates compile failure"() {
+        def failure
+        target.execute(spec) >> { throw failure = new CompilationFailedException() }
+
+        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.scalaCompileOptions.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(_) >> { ScalaJavaJointCompileSpec 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/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGeneratorTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGeneratorTest.groovy
new file mode 100644
index 0000000..6a75e6f
--- /dev/null
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/ScalaCompilerArgumentsGeneratorTest.groovy
@@ -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.api.internal.tasks.scala
+
+import spock.lang.Specification
+
+class ScalaCompilerArgumentsGeneratorTest extends Specification {
+    def generator = new ScalaCompilerArgumentsGenerator()
+    def spec = new DefaultScalaCompileSpec()
+
+    def "default options"() {
+        expect:
+        generator.generate(spec) as Set == ["-deprecation", "-unchecked"] as Set
+    }
+
+    def "can suppress deprecation flag"() {
+        spec.scalaCompileOptions.deprecation = false
+
+        expect:
+        !generator.generate(spec).contains("-deprecation")
+    }
+
+    def "can suppress unchecked flag"() {
+        spec.scalaCompileOptions.unchecked = false
+
+        expect:
+        !generator.generate(spec).contains("-unchecked")
+    }
+
+    def "generates debug level option"() {
+        spec.scalaCompileOptions.debugLevel = "someLevel"
+
+        expect:
+        generator.generate(spec).contains("-g:someLevel")
+    }
+
+    def "generates optimize flag"() {
+        spec.scalaCompileOptions.optimize = true
+
+        expect:
+        generator.generate(spec).contains("-optimise")
+    }
+
+    def "generates encoding option"() {
+        spec.scalaCompileOptions.encoding = "some encoding"
+
+        when:
+        def args = generator.generate(spec)
+
+        then:
+        args.contains("-encoding")
+        args.contains("some encoding")
+    }
+
+    def "generates verbose flag"() {
+        spec.scalaCompileOptions.debugLevel = "verbose"
+
+        expect:
+        generator.generate(spec).contains("-verbose")
+    }
+
+    def "generates debug flag"() {
+        spec.scalaCompileOptions.debugLevel = "debug"
+
+        expect:
+        generator.generate(spec).contains("-Ydebug")
+    }
+
+    def "generates logging phases options"() {
+        spec.scalaCompileOptions.loggingPhases = ["foo", "bar", "baz"]
+
+        when:
+        def args = generator.generate(spec)
+
+        then:
+        args.contains("-Ylog:foo")
+        args.contains("-Ylog:bar")
+        args.contains("-Ylog:baz")
+    }
+
+    def "adds any additional parameters"() {
+        spec.scalaCompileOptions.additionalParameters = ["-other", "value"]
+
+        when:
+        def args = generator.generate(spec)
+
+        then:
+        args.contains("-other")
+        args.contains("value")
+    }
+}
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 8818f37..c6dacfe 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
@@ -19,10 +19,14 @@ 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.bundling.Jar
 import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
+import org.gradle.api.artifacts.Configuration
 import org.gradle.util.HelperUtil
+
 import org.junit.Test
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.Matchers.isEmpty
 import static org.gradle.util.WrapUtil.toLinkedSet
@@ -35,12 +39,12 @@ public class ScalaBasePluginTest {
     private final Project project = HelperUtil.createRootProject()
     private final ScalaBasePlugin scalaPlugin = new ScalaBasePlugin()
 
-    @Test public void appliesTheJavaPluginToTheProject() {
+    @Test void appliesTheJavaPluginToTheProject() {
         scalaPlugin.apply(project)
         assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin))
     }
 
-    @Test public void addsScalaToolsConfigurationToTheProject() {
+    @Test void addsScalaToolsConfigurationToTheProject() {
         scalaPlugin.apply(project)
         def configuration = project.configurations.getByName(ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME)
         assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
@@ -48,7 +52,33 @@ public class ScalaBasePluginTest {
         assertTrue(configuration.transitive)
     }
 
-    @Test public void addsScalaConventionToNewSourceSet() {
+    @Test void addsZincConfigurationToTheProject() {
+        scalaPlugin.apply(project)
+        def configuration = project.configurations.getByName(ScalaBasePlugin.ZINC_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+
+    @Test void preconfiguresZincClasspathForCompileTasksThatUseZinc() {
+        scalaPlugin.apply(project)
+        project.sourceSets.add('custom')
+        def task = project.tasks.compileCustomScala
+        task.scalaCompileOptions.useAnt = false
+        assert task.zincClasspath instanceof Configuration
+        assert task.zincClasspath.dependencies.find { it.name.contains('zinc') }
+    }
+
+    @Test void doesNotPreconfigureZincClasspathForCompileTasksThatUseAnt() {
+        scalaPlugin.apply(project)
+        project.sourceSets.add('custom')
+        def task = project.tasks.compileCustomScala
+        task.scalaCompileOptions.useAnt = true
+        assert task.zincClasspath instanceof Configuration
+        assert task.zincClasspath.empty
+    }
+
+    @Test void addsScalaConventionToNewSourceSet() {
         scalaPlugin.apply(project)
 
         def sourceSet = project.sourceSets.add('custom')
@@ -56,7 +86,7 @@ public class ScalaBasePluginTest {
         assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/custom/scala"))))
     }
 
-    @Test public void addsCompileTaskForNewSourceSet() {
+    @Test void addsCompileTaskForNewSourceSet() {
         scalaPlugin.apply(project)
 
         project.sourceSets.add('custom')
@@ -68,8 +98,34 @@ public class ScalaBasePluginTest {
         assertThat(task.source as List, equalTo(project.sourceSets.custom.scala as List))
         assertThat(task, dependsOn('compileCustomJava'))
     }
+
+    @Test void preconfiguresIncrementalCompileOptions() {
+        scalaPlugin.apply(project)
+
+        project.sourceSets.add('custom')
+        project.tasks.add('customJar', Jar)
+        ScalaCompile task = project.tasks['compileCustomScala']
+        project.gradle.buildListenerBroadcaster.projectsEvaluated(project.gradle)
+
+        assertThat(task.scalaCompileOptions.incrementalOptions.analysisFile, equalTo(new File("$project.buildDir/tmp/scala/compilerAnalysis/compileCustomScala.analysis")))
+        assertThat(task.scalaCompileOptions.incrementalOptions.publishedCode, equalTo(project.tasks['customJar'].archivePath))
+    }
+
+    @Test void incrementalCompileOptionsCanBeOverridden() {
+        scalaPlugin.apply(project)
+
+        project.sourceSets.add('custom')
+        project.tasks.add('customJar', Jar)
+        ScalaCompile task = project.tasks['compileCustomScala']
+        task.scalaCompileOptions.incrementalOptions.analysisFile = new File("/my/file")
+        task.scalaCompileOptions.incrementalOptions.publishedCode = new File("/my/published/code.jar")
+        project.gradle.buildListenerBroadcaster.projectsEvaluated(project.gradle)
+
+        assertThat(task.scalaCompileOptions.incrementalOptions.analysisFile, equalTo(new File("/my/file")))
+        assertThat(task.scalaCompileOptions.incrementalOptions.publishedCode, equalTo(new File("/my/published/code.jar")))
+    }
     
-    @Test public void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
+    @Test void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
         scalaPlugin.apply(project)
 
         project.sourceSets.add('custom')
@@ -77,7 +133,7 @@ public class ScalaBasePluginTest {
         assertThat(task, dependsOn(hasItem('compileCustomScala')))
     }
 
-    @Test public void configuresCompileTasksDefinedByTheBuildScript() {
+    @Test void configuresCompileTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
         def task = project.task('otherCompile', type: ScalaCompile)
@@ -86,7 +142,7 @@ public class ScalaBasePluginTest {
         assertThat(task, dependsOn())
     }
 
-    @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
+    @Test void configuresScalaDocTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
         def task = project.task('otherScaladoc', type: ScalaDoc)
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy
index 35fc105..79f0421 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy
@@ -20,15 +20,15 @@ import org.junit.Test
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.*
 
-public class ScalaCompileOptionsTest {
+class ScalaCompileOptionsTest {
 
-    private ScalaCompileOptions compileOptions;
+    private ScalaCompileOptions compileOptions
 
-    @Before public void setUp() {
+    @Before void setUp() {
         compileOptions = new ScalaCompileOptions()
     }
 
-    @Test public void testOptionMapDoesNotContainCompileDaemon() {
+    @Test void testOptionMapDoesNotContainCompileDaemon() {
         String antProperty = 'useCompileDaemon'
         assertFalse(compileOptions.useCompileDaemon)
         assertFalse(compileOptions.optionMap().containsKey(antProperty))
@@ -37,51 +37,47 @@ public class ScalaCompileOptionsTest {
         assertFalse(compileOptions.optionMap().containsKey(antProperty))
     }
 
-    @Test public void testOptionMapContainsDaemonServerIfSpecified() {
+    @Test void testOptionMapContainsDaemonServerIfSpecified() {
         assertSimpleStringValue('daemonServer', 'server', null, 'host:9000')
     }
 
-    @Test public void testOptionMapContainsFailOnError() {
-        assertBooleanValue('failOnError', 'failonerror', true)
+    @Test void testOptionMapContainsFailOnError() {
+        assertBooleanValue('failOnError', 'failOnError', true)
     }
 
-    @Test public void testOptionMapContainsDeprecation() {
+    @Test void testOptionMapContainsDeprecation() {
         assertOnOffValue('deprecation', 'deprecation', true)
     }
 
-    @Test public void testOptionMapContainsUnchecked() {
+    @Test void testOptionMapContainsUnchecked() {
         assertOnOffValue('unchecked', 'unchecked', true)
     }
 
-    @Test public void testOptionMapContainsDebugLevelIfSpecified() {
-        assertSimpleStringValue('debugLevel', 'debugInfo', null, 'line')
+    @Test void testOptionMapContainsDebugLevelIfSpecified() {
+        assertSimpleStringValue('debugLevel', 'debuginfo', null, 'line')
     }
 
-    @Test public void testOptionMapContainsOptimize() {
+    @Test void testOptionMapContainsOptimize() {
         assertFalse(compileOptions.optionMap().containsKey('optimise'))
 
         compileOptions.optimize = true
         assertThat(compileOptions.optionMap()['optimise'], equalTo('on'))
     }
 
-    @Test public void testOptionMapContainsEncodingIfSpecified() {
+    @Test void testOptionMapContainsEncodingIfSpecified() {
         assertSimpleStringValue('encoding', 'encoding', null, 'utf8')
     }
 
-    @Test public void testOptionMapContainsForce() {
+    @Test void testOptionMapContainsForce() {
         assertSimpleStringValue('force', 'force', 'never', 'changed')
     }
 
-    @Test public void testOptionMapContainsTargetCompatibility() {
-        String antProperty = 'target'
-        assertThat(compileOptions.targetCompatibility as String, equalTo('1.5'))
-        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('jvm-1.5'))
-        compileOptions.targetCompatibility = '1.4'
-        assertThat(compileOptions.optionMap()['target'] as String, equalTo('jvm-1.4'))
+    @Test void testOptionMapDoesNotContainTargetCompatibility() {
+        assert !compileOptions.optionMap().containsKey("target")
     }
 
-    @Test public void testOptionMapContainsValuesForAdditionalParameters() {
-        String antProperty = 'addParams'
+    @Test void testOptionMapContainsValuesForAdditionalParameters() {
+        String antProperty = 'addparams'
         assertNull(compileOptions.additionalParameters)
         assertFalse(compileOptions.optionMap().containsKey(antProperty))
 
@@ -89,16 +85,16 @@ public class ScalaCompileOptionsTest {
         assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('-opt1 -opt2' as String))
     }
 
-    @Test public void testOptionMapContainsListFiles() {
-        assertBooleanValue('listFiles', 'scalacDebugging', false)
+    @Test void testOptionMapContainsListFiles() {
+        assertBooleanValue('listFiles', 'scalacdebugging', false)
     }
 
-    @Test public void testOptionMapContainsLoggingLevelIfSpecified() {
+    @Test void testOptionMapContainsLoggingLevelIfSpecified() {
         assertSimpleStringValue('loggingLevel', 'logging', null, 'verbose')
     }
 
-    @Test public void testOptionMapContainsValueForLoggingPhase() {
-        String antProperty = 'logPhase'
+    @Test void testOptionMapContainsValueForLoggingPhase() {
+        String antProperty = 'logphase'
         Map optionMap = compileOptions.optionMap()
         assertFalse(optionMap.containsKey(antProperty))
 
@@ -107,7 +103,14 @@ public class ScalaCompileOptionsTest {
         assertThat(optionMap[antProperty] as String, equalTo('pickler,tailcalls' as String))
     }
 
-    private def assertBooleanValue(String fieldName, String antProperty, boolean defaultValue) {
+    @Test void disablingUseAntEnablesFork() {
+        assert !compileOptions.fork
+
+        compileOptions.useAnt = false
+        assert compileOptions.fork
+    }
+
+    private assertBooleanValue(String fieldName, String antProperty, boolean defaultValue) {
         assertThat(compileOptions."$fieldName" as boolean, equalTo(defaultValue))
 
         compileOptions."$fieldName" = true
@@ -117,7 +120,7 @@ public class ScalaCompileOptionsTest {
         assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('false'))
     }
 
-    private def assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
+    private assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
         assertThat(compileOptions."$fieldName" as boolean, equalTo(defaultValue))
 
         compileOptions."$fieldName" = true
@@ -127,7 +130,7 @@ public class ScalaCompileOptionsTest {
         assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('off'))
     }
 
-    private def assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
+    private assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
         assertThat(compileOptions."${fieldName}" as String, equalTo(defaultValue))
         if (defaultValue == null) {
             assertFalse(compileOptions.optionMap().containsKey(antProperty))
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 adbe004..af416aa 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
@@ -48,10 +48,8 @@ public class ScalaCompileTest extends AbstractCompileTest {
         return scalaCompile;
     }
 
-    @Override
     @Before
     public void setUp() {
-        super.setUp();
         scalaCompile = createTask(ScalaCompile.class);
         scalaCompiler = context.mock(Compiler.class);
         scalaCompile.setCompiler(scalaCompiler);
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy
index ec8bd60..61fd1c0 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.api.tasks.scala
 
 import org.junit.Test
+
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.*
 
@@ -73,7 +74,7 @@ public class ScalaDocOptionsTest {
         assertThat(docOptions.optionMap()[antProperty] as String, equalTo('-opt1 -opt2' as String))
     }
 
-    private def assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
+    private assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
         assertThat(docOptions."$fieldName" as boolean, equalTo(defaultValue))
 
         docOptions."$fieldName" = true
@@ -83,7 +84,7 @@ public class ScalaDocOptionsTest {
         assertThat(docOptions.optionMap()[antProperty] as String, equalTo('off'))
     }
 
-    private def assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
+    private assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
         assertThat(docOptions."${fieldName}" as String, equalTo(defaultValue))
         if (defaultValue == null) {
             assertFalse(docOptions.optionMap().containsKey(antProperty))
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java
index 1d63e79..bfa6d9f 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java
@@ -48,10 +48,8 @@ public class ScalaDocTest extends AbstractTaskTest {
         return scalaDoc;
     }
 
-    @Override
     @Before
     public void setUp() {
-        super.setUp();
         destDir = getProject().file("destDir");
         srcDir = getProject().file("src");
         GFileUtils.touch(new File(srcDir, "file.scala"));
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
index ca72b22..24255fa 100644
--- a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
@@ -16,9 +16,11 @@
 
 package org.gradle.plugins.signing
 
-import org.gradle.integtests.fixtures.*
-
-import org.junit.*
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.UsesSample
+import org.junit.Rule
 
 class SigningSamplesSpec extends AbstractIntegrationSpec {
     @Rule public final Sample mavenSample = new Sample()
@@ -51,6 +53,6 @@ class SigningSamplesSpec extends AbstractIntegrationSpec {
     }
 
     MavenRepository getRepo() {
-        return new MavenRepository(mavenSample.dir.file("build/repo"))
+        return maven(mavenSample.dir.file("build/repo"))
     }
 }
\ No newline at end of file
diff --git a/subprojects/sonar/sonar.gradle b/subprojects/sonar/sonar.gradle
index 85f953f..8400bb5 100644
--- a/subprojects/sonar/sonar.gradle
+++ b/subprojects/sonar/sonar.gradle
@@ -23,6 +23,7 @@ dependencies {
     compile project(":plugins")
     compile libraries.guava
     compile libraries.slf4j_api
+    compile libraries.inject
     compile "org.codehaus.sonar:sonar-batch-bootstrapper:2.9 at jar"
 
     // minimal dependencies to make our code compile
@@ -30,6 +31,10 @@ dependencies {
     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"
+
+    //integTestRuntime "com.h2database:h2:1.3.168"
+    integTestRuntime "org.gradle.sonar:sonar-test-server:3.2 at war"
+    integTestRuntime "org.gradle.sonar:sonar-test-server-home-dir:3.2 at zip"
 }
 
 useTestFixtures()
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
new file mode 100644
index 0000000..7d53643
--- /dev/null
+++ b/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
@@ -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.plugins.sonar
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.webapp.WebAppContext
+import org.gradle.integtests.fixtures.TestResources
+
+import org.junit.Rule
+import spock.lang.Shared
+import spock.lang.AutoCleanup
+import org.gradle.util.Resources
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.ClasspathUtil
+import org.gradle.util.AvailablePortFinder
+
+class SonarSmokeIntegrationTest extends AbstractIntegrationSpec {
+    @Shared
+    AvailablePortFinder portFinder = AvailablePortFinder.createPrivate()
+
+    @AutoCleanup("stop")
+    Server webServer = new Server(0)
+
+    @Rule
+    TemporaryFolder tempDir = new TemporaryFolder()
+
+    @Rule
+    TestResources testResources
+
+    int databasePort
+
+    def setup() {
+        def classpath = ClasspathUtil.getClasspath(getClass().classLoader).collect() { new File(it.toURI()) }
+        def warFile = classpath.find { it.name == "sonar-test-server-3.2.war" }
+        assert warFile
+        def zipFile = classpath.find { it.name == "sonar-test-server-home-dir-3.2.zip" }
+        assert zipFile
+
+        def sonarHome = tempDir.createDir("sonar-home")
+        System.setProperty("SONAR_HOME", sonarHome.path)
+        new AntBuilder().unzip(src: zipFile, dest: sonarHome, overwrite: true)
+
+        databasePort = portFinder.nextAvailable
+        sonarHome.file("conf/sonar.properties") << """
+sonar.jdbc.username=sonar
+sonar.jdbc.password=sonar
+sonar.jdbc.url=jdbc:h2:mem:sonartest
+sonar.embeddedDatabase.port=$databasePort
+        """.trim()
+
+        def context = new WebAppContext()
+        context.war = warFile
+        webServer.addHandler(context)
+        webServer.start()
+    }
+
+    def "can run Sonar analysis"() {
+        distribution.requireIsolatedDaemons()
+        // Without forking, we run into problems with Sonar's BootStrapClassLoader, at least when running from IDEA.
+        // Problem is that BootStrapClassLoader, although generally isolated from its parent(s), always
+        // delegates to the system class loader. That class loader holds the test class path and therefore
+        // also the Sonar dependencies with "provided" scope. Hence, the Sonar dependencies get loaded by
+        // the wrong class loader.
+        when:
+        executer.withForkingExecuter()
+                .withArgument("-PserverUrl=http://localhost:${webServer.connectors[0].localPort}")
+                .withArgument("-PdatabaseUrl=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
+                .withTasks("sonarAnalyze").run()
+
+        then:
+        noExceptionThrown()
+
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..ee5346c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: "java"
+apply plugin:  "sonar"
+
+sonar {
+    server {
+        url serverUrl
+    }
+    database {
+        //url = "jdbc:h2:tcp://localhost:9092/sonar"
+        url databaseUrl
+        driverClassName = "org.h2.Driver"
+    }
+}
+
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/src/main/java/Person.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/src/main/java/Person.java
new file mode 100644
index 0000000..1395185
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/src/main/java/Person.java
@@ -0,0 +1,5 @@
+public class Person {
+    public String getName() {
+        return "Fred";
+    }
+}
\ 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
index ba5061f..e1a0e20 100644
--- 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
@@ -23,7 +23,7 @@ import org.gradle.util.ClasspathUtil
 import org.gradle.api.plugins.sonar.model.SonarRootModel
 
 /**
- * Analyzes a project hierachy and writes the results to the
+ * Analyzes a project hierarchy and writes the results to the
  * Sonar database.
  */
 class SonarAnalyze extends ConventionTask {
@@ -40,7 +40,7 @@ class SonarAnalyze extends ConventionTask {
         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")
+                                "org.gradle.api.plugins.sonar.model", "ch.qos.logback")
 
         def analyzerClass = classLoader.loadClass("org.gradle.api.plugins.sonar.internal.SonarCodeAnalyzer")
         def analyzer = analyzerClass.newInstance()
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 d326bd7..d744c34 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
@@ -25,6 +25,8 @@ import org.gradle.util.GradleVersion
 import org.gradle.internal.jvm.Jvm
 import org.gradle.api.plugins.sonar.model.*
 
+import javax.inject.Inject
+
 /**
  * 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>
@@ -42,10 +44,14 @@ import org.gradle.api.plugins.sonar.model.*
 class SonarPlugin implements Plugin<ProjectInternal> {
     static final String SONAR_ANALYZE_TASK_NAME = "sonarAnalyze"
 
-    private Instantiator instantiator
+    private final Instantiator instantiator
+
+    @Inject
+    SonarPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator
+    }
 
     void apply(ProjectInternal project) {
-        instantiator = project.services.get(Instantiator)
         def task = configureSonarTask(project)
         def model = configureSonarRootModel(project)
         task.rootModel = model
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
index 7796351..a4790c3 100644
--- 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
@@ -36,7 +36,7 @@ class SamplesToolingApiIntegrationTest extends Specification {
         result.output.contains("src/main/java")
     }
 
-    @UsesSample('toolingApi/build')
+    @UsesSample('toolingApi/runBuild')
     def "can use tooling API to run tasks"() {
         tweakProject()
 
@@ -83,19 +83,14 @@ repositories {
     maven { url "${distribution.libsRepo.toURI()}" }
 }
 run {
-    args = ["${TextUtil.escapeString(distribution.gradleHomeDir.absolutePath)}"]
+    args = ["${TextUtil.escapeString(distribution.gradleHomeDir.absolutePath)}", "${TextUtil.escapeString(distribution.userHomeDir.absolutePath)}"]
+    systemProperty 'org.gradle.daemon.idletimeout', 10000
+    systemProperty 'org.gradle.daemon.registry.base', "${TextUtil.escapeString(projectDir.file("daemon").absolutePath)}"
 }
 """ + buildScript.substring(index)
 
         buildFile.text = buildScript
 
-        // Tweak the build environment
-        Properties props = new Properties()
-        props['org.gradle.daemon.idletimeout'] = '60000'
-        projectDir.file('gradle.properties').withOutputStream {outstr ->
-            props.store(outstr, 'props')
-        }
-
         // Add in an empty settings file to avoid searching up
         projectDir.file('settings.gradle').text = '// to stop search upwards'
     }
@@ -104,6 +99,7 @@ run {
         try {
             return new GradleDistributionExecuter(distribution).inDirectory(sample.dir)
                     .withTasks('run')
+                    .withDaemonIdleTimeoutSecs(60)
                     .run()
         } catch (Exception e) {
             throw new IntegrationTestHint(e);
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
index dfc1fe7..b1825be 100644
--- 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
@@ -25,6 +25,9 @@ import org.gradle.util.GradleVersion
 import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
+import spock.lang.Issue
+import org.gradle.integtests.tooling.fixture.TextUtil
+import org.gradle.integtests.fixtures.GradleHandle
 
 class ToolingApiIntegrationTest extends Specification {
     @Rule public final GradleDistribution dist = new GradleDistribution()
@@ -48,6 +51,8 @@ class ToolingApiIntegrationTest extends Specification {
     }
 
     def "tooling api uses the wrapper properties to determine which version to use"() {
+        toolingApi.isEmbedded = false
+
         projectDir.file('build.gradle').text = """
 task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
 task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
@@ -65,6 +70,8 @@ task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
     }
 
     def "tooling api searches up from the project directory to find the wrapper properties"() {
+        toolingApi.isEmbedded = false
+
         projectDir.file('settings.gradle') << "include 'child'"
         projectDir.file('build.gradle') << """
 task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
@@ -88,6 +95,7 @@ allprojects {
     }
 
     def "can specify a gradle installation to use"() {
+        toolingApi.isEmbedded = false
         projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
 
         when:
@@ -101,6 +109,7 @@ allprojects {
     }
 
     def "can specify a gradle distribution to use"() {
+        toolingApi.isEmbedded = false
         projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
 
         when:
@@ -114,6 +123,7 @@ allprojects {
     }
 
     def "can specify a gradle version to use"() {
+        toolingApi.isEmbedded = false
         projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
 
         when:
@@ -131,10 +141,143 @@ allprojects {
 
         when:
         toolingApi.withConnector { connector -> connector.useDistribution(dist.toURI()) }
-        def e = toolingApi.maybeFailWithConnection { connection -> connection.getModel(GradleProject.class) }
+        toolingApi.maybeFailWithConnection { connection -> connection.getModel(GradleProject.class) }
 
         then:
-        e.class == UnsupportedVersionException
+        UnsupportedVersionException e = thrown()
         e.message == "The specified Gradle distribution '${dist.toURI()}' is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
     }
+
+    @Issue("GRADLE-2419")
+    def "tooling API does not hold JVM open"() {
+        given:
+        def buildFile = projectDir.file("build.gradle")
+        def startTimeoutMs = 60000
+        def stateChangeTimeoutMs = 15000
+        def stopTimeoutMs = 10000
+        def retryIntervalMs = 500
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'application'
+
+            repositories {
+                maven { url "${dist.libsRepo.toURI()}" }
+                maven { url "http://repo.gradle.org/gradle/repo" }
+            }
+
+            dependencies {
+                compile "org.gradle:gradle-tooling-api:${dist.version}"
+                runtime 'org.slf4j:slf4j-simple:1.6.6'
+            }
+
+            mainClassName = 'Main'
+
+            run {
+                args = ["${TextUtil.escapeString(dist.gradleHomeDir.absolutePath)}", "${TextUtil.escapeString(dist.userHomeDir.absolutePath)}"]
+                systemProperty 'org.gradle.daemon.idletimeout', 10000
+                systemProperty 'org.gradle.daemon.registry.base', "${TextUtil.escapeString(projectDir.file("daemon").absolutePath)}"
+            }
+
+            task thing << {
+                def startMarkerFile = file("start.marker")
+                startMarkerFile << new Date().toString()
+                println "start marker written (\$startMarkerFile)"
+
+                def stopMarkerFile = file("stop.marker")
+                def startedAt = System.currentTimeMillis()
+                println "waiting for stop marker (\$stopMarkerFile)"
+                while(!stopMarkerFile.exists()) {
+                    if (System.currentTimeMillis() - startedAt > $stateChangeTimeoutMs) {
+                        throw new Exception("Timeout ($stateChangeTimeoutMs ms) waiting for stop marker")
+                    }
+                    sleep $retryIntervalMs
+                }
+            }
+        """
+
+        projectDir.file("src/main/java/Main.java") << """
+            import org.gradle.tooling.BuildLauncher;
+            import org.gradle.tooling.GradleConnector;
+            import org.gradle.tooling.ProjectConnection;
+
+            import java.io.ByteArrayOutputStream;
+            import java.io.File;
+            import java.lang.System;
+
+            public class Main {
+                public static void main(String[] args) {
+                    // Configure the connector and create the connection
+                    GradleConnector connector = GradleConnector.newConnector();
+
+                    if (args.length > 0) {
+                        connector.useInstallation(new File(args[0]));
+                        if (args.length > 1) {
+                            connector.useGradleUserHomeDir(new File(args[1]));
+                        }
+                    }
+
+                    connector.forProjectDirectory(new File("."));
+                    if (args.length > 0) {
+                        connector.useInstallation(new File(args[0]));
+                    }
+
+                    ProjectConnection connection = connector.connect();
+                    try {
+                        // Configure the build
+                        BuildLauncher launcher = connection.newBuild();
+                        launcher.forTasks("thing").withArguments("-u");
+                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                        launcher.setStandardOutput(outputStream);
+                        launcher.setStandardError(outputStream);
+
+                        // Run the build
+                        launcher.run();
+                    } finally {
+                        // Clean up
+                        connection.close();
+                    }
+                }
+            }
+        """
+
+        when:
+        GradleHandle handle = dist.executer()
+                .inDirectory(projectDir)
+                .withTasks('run')
+                .withDaemonIdleTimeoutSecs(60)
+                .start()
+
+        then:
+        // Wait for the tooling API to start the build
+        def startMarkerFile = projectDir.file("start.marker")
+        def foundStartMarker = startMarkerFile.exists()
+        def startAt = System.currentTimeMillis()
+        while (handle.running && !foundStartMarker) {
+            if (System.currentTimeMillis() - startAt > startTimeoutMs) {
+                throw new Exception("timeout waiting for start marker")
+            } else {
+                sleep retryIntervalMs
+            }
+            foundStartMarker = startMarkerFile.exists()
+        }
+
+        if (!foundStartMarker) {
+            throw new Exception("Build finished without start marker appearing")
+        }
+
+        // Signal the build to finish
+        def stopMarkerFile = projectDir.file("stop.marker")
+        def stopMarkerAt = System.currentTimeMillis()
+        stopMarkerFile << new Date().toString()
+
+        // Does the tooling API hold the JVM open (which will also hold the build open)?
+        while (handle.running) {
+            if (System.currentTimeMillis() - stopMarkerAt > stopTimeoutMs) {
+                throw new Exception("timeout after placing stop marker (JVM might have been held open")
+            }
+        }
+
+        handle.waitForFinish()
+    }
 }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy
new file mode 100644
index 0000000..8036678
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiRemoteIntegrationTest.groovy
@@ -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.integtests.tooling
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.tooling.BuildLauncher
+import org.gradle.util.GradleVersion
+import org.junit.Rule
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+import static org.hamcrest.Matchers.containsString
+import static org.junit.Assert.assertThat
+
+class ToolingApiRemoteIntegrationTest extends AbstractIntegrationSpec {
+    @Rule HttpServer server = new HttpServer()
+    final ToolingApi toolingApi = new ToolingApi(distribution, distribution.userHomeDir, { getTestDir() }, false)
+
+    void setup() {
+        server.start()
+        settingsFile << "";
+        buildFile << "task hello << { println hello }"
+    }
+
+    public void "downloads distribution with valid useragent information"() {
+        assert distribution.binDistribution.exists(): "bin distribution must exist to run this test, you need to run the :binZip task"
+        expect:
+        server.allowGetOrHead("/dist", distribution.binDistribution)
+        server.expectUserAgent(matchesNameAndVersion("Gradle Tooling API", GradleVersion.current().getVersion()))
+        when:
+        URI gradleDistributionURI = URI.create("http://localhost:${server.port}/dist")
+
+        and:
+        toolingApi.withConnector {
+            it.useDistribution(gradleDistributionURI)
+        }
+
+        ByteArrayOutputStream buildOutput = new ByteArrayOutputStream()
+
+        toolingApi.withConnection { connection ->
+            BuildLauncher launcher = connection.newBuild().forTasks("hello")
+            launcher.standardOutput = buildOutput;
+            launcher.run()
+        }
+
+        then:
+        assertThat(new String(buildOutput.toByteArray()), containsString('hello'))
+    }
+}
\ No newline at end of file
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
index ed08ec6..c79b0d9 100644
--- 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
@@ -15,7 +15,6 @@
  */
 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
@@ -26,17 +25,18 @@ import org.gradle.tooling.UnsupportedVersionException
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
+import java.util.concurrent.TimeUnit
+
 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
+    boolean verboseLogging = LOGGER.debugEnabled
 
     ToolingApi(GradleDistribution dist) {
         this(dist, dist.userHomeDir, { dist.testDir }, GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.embedded)
@@ -66,13 +66,12 @@ class ToolingApi {
         }
     }
 
-    public Throwable maybeFailWithConnection(Closure cl) {
+    public void maybeFailWithConnection(Closure cl) {
         GradleConnector connector = connector()
         try {
             withConnectionRaw(connector, cl)
-            return null
         } catch (Throwable e) {
-            return e
+            throw e
         }
     }
 
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
index 6168b14..54d8034 100755
--- 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
@@ -125,12 +125,14 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
         }
 
         private ClassLoader getTestClassLoader() {
-            def classLoader = TEST_CLASS_LOADERS.get(toolingApi.version)
-            if (!classLoader) {
-                classLoader = createTestClassLoader()
-                TEST_CLASS_LOADERS.put(toolingApi.version, classLoader)
+            synchronized(ToolingApiCompatibilitySuiteRunner) {
+                def classLoader = TEST_CLASS_LOADERS.get(toolingApi.version)
+                if (!classLoader) {
+                    classLoader = createTestClassLoader()
+                    TEST_CLASS_LOADERS.put(toolingApi.version, classLoader)
+                }
+                return classLoader
             }
-            return classLoader
         }
 
         private ClassLoader createTestClassLoader() {
@@ -151,6 +153,7 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
             sharedClassLoader.allowClass(OperatingSystem)
             sharedClassLoader.allowClass(Requires)
             sharedClassLoader.allowClass(TestPrecondition)
+            sharedClassLoader.allowResources(target.name.replace('.', '/'))
 
             def parentClassLoader = new MultiParentClassLoader(toolingApi.classLoader, sharedClassLoader)
 
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
index 27d732a..750cd12 100644
--- 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
@@ -26,6 +26,7 @@ import org.junit.runner.RunWith
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
+import org.gradle.util.TestFile
 
 @RunWith(ToolingApiCompatibilitySuiteRunner)
 abstract class ToolingApiSpecification extends Specification {
@@ -80,7 +81,20 @@ abstract class ToolingApiSpecification extends Specification {
         toolingApi.connector()
     }
 
-    Throwable maybeFailWithConnection(Closure cl) {
+    void maybeFailWithConnection(Closure cl) {
         toolingApi.maybeFailWithConnection(cl)
     }
+
+    TestFile getProjectDir() {
+        dist.testDir
+    }
+
+    TestFile getBuildFile() {
+        file("build.gradle")
+    }
+
+    TestFile file(Object... path) {
+        projectDir.file(path)
+    }
+
 }
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
index e41f620..c1e8aba 100644
--- 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
@@ -78,6 +78,19 @@ description = 'this is a project'
         fullProject.projectDependencies.empty
     }
 
+    def "does not run any tasks when fetching model"() {
+        when:
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+gradle.taskGraph.beforeTask { throw new RuntimeException() }
+'''
+        HierarchicalEclipseProject project = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        project != null
+    }
+
     def "can build the eclipse source directories for a java project"() {
         def projectDir = dist.testDir
         projectDir.file('build.gradle').text = "apply plugin: 'java'"
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
index 57128ba..848f522 100644
--- 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
@@ -36,8 +36,11 @@ task b
         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
+        def tasks = project.tasks*.name
+        tasks.contains('a')
+        tasks.contains('b')
+        !tasks.contains('cleanEclipse')
+        !tasks.contains('eclipse')
     }
 
     @Issue("GRADLE-1529")
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
index d3ae85f..b30ac63 100644
--- 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
@@ -15,7 +15,7 @@
  */
 package org.gradle.integtests.tooling.m5
 
-import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.MavenFileRepository
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
@@ -165,7 +165,7 @@ idea.module.excludeDirs += file('foo')
         def projectDir = dist.testDir
         def fakeRepo = projectDir.file("repo")
 
-        def dependency = new MavenRepository(fakeRepo).module("foo.bar", "coolLib", 1.0)
+        def dependency = new MavenFileRepository(fakeRepo).module("foo.bar", "coolLib", 1.0)
         dependency.artifact(classifier: 'sources')
         dependency.artifact(classifier: 'javadoc')
         dependency.publish()
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
index c3f8ba3..c9e400c 100644
--- 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
@@ -21,7 +21,6 @@ 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
@@ -43,13 +42,14 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
     def "fails eagerly when java home unsupported for model"() {
         def java = AvailableJavaHomes.bestAlternative
         when:
-        Exception e = maybeFailWithConnection {
+        maybeFailWithConnection {
             def model = it.model(BuildEnvironment.class)
             model.setJavaHome(java)
             model.get()
         }
 
         then:
+        UnsupportedOperationConfigurationException e = thrown()
         assertExceptionInformative(e, "setJavaHome()")
     }
 
@@ -57,43 +57,44 @@ class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification
     def "fails eagerly when java home unsupported for build"() {
         def java = AvailableJavaHomes.bestAlternative
         when:
-        Exception e = maybeFailWithConnection {
+        maybeFailWithConnection {
             def build = it.newBuild()
             build.setJavaHome(java)
             build.forTasks('tasks').run()
         }
 
         then:
+        UnsupportedOperationConfigurationException e = thrown()
         assertExceptionInformative(e, "setJavaHome()")
     }
 
     def "fails eagerly when java args unsupported"() {
         when:
-        Exception e = maybeFailWithConnection {
+        maybeFailWithConnection {
             def model = it.model(BuildEnvironment.class)
             model.setJvmArguments("-Xmx512m")
             model.get()
         }
 
         then:
+        UnsupportedOperationConfigurationException e = thrown()
         assertExceptionInformative(e, "setJvmArguments()")
     }
 
     def "fails eagerly when standard input unsupported"() {
         when:
-        Exception e = maybeFailWithConnection {
+        maybeFailWithConnection {
             def model = it.model(BuildEnvironment.class)
             model.setStandardInput(new ByteArrayInputStream('yo!'.bytes))
             model.get()
         }
 
         then:
+        UnsupportedOperationConfigurationException e = thrown()
         assertExceptionInformative(e, "setStandardInput()")
     }
 
-    void assertExceptionInformative(Exception actual, String expectedMessageSubstring) {
-        assert actual instanceof GradleConnectionException
-        assert actual instanceof UnsupportedOperationConfigurationException
+    void assertExceptionInformative(UnsupportedOperationConfigurationException actual, String expectedMessageSubstring) {
         assert !actual.message.contains(Exceptions.INCOMPATIBLE_VERSION_HINT) //no need for hint, the message is already good
         assert actual.message.contains(expectedMessageSubstring)
 
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
index 5074015..413c4fc 100644
--- 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
@@ -20,7 +20,6 @@ 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
 
 @MinToolingApiVersion('1.0-milestone-8')
 @MinTargetGradleVersion('1.0-milestone-3')
@@ -34,10 +33,10 @@ class UnknownModelFeedbackCrossVersionSpec extends ToolingApiSpecification {
         //the daemon breaks and does not return anything to the client making the client waiting forever.
 
         when:
-        def e = maybeFailWithConnection { it.getModel(UnknownModel.class) }
+        maybeFailWithConnection { it.getModel(UnknownModel.class) }
 
         then:
-        e instanceof UnsupportedVersionException
-        e instanceof UnknownModelException
+        UnknownModelException e = thrown()
+        e.message.contains('Unknown model: \'UnknownModel\'.')
     }
 }
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
index 6858744..6ccde3d 100644
--- 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
@@ -35,14 +35,14 @@ class DaemonErrorFeedbackCrossVersionSpec extends ToolingApiSpecification {
         toolingApi.isEmbedded = false
 
         when:
-        def ex = maybeFailWithConnection {
+        maybeFailWithConnection {
             it.newBuild()
                     .setJvmArguments("-Xasdf")
                     .run()
         }
 
         then:
-        ex instanceof GradleConnectionException
+        GradleConnectionException ex = thrown()
         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
index 1e033ff..3146969 100644
--- 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
@@ -59,12 +59,12 @@ class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
         def dummyJdk = dist.file("wrong jdk location").createDir()
 
         when:
-        def ex = maybeFailWithConnection {
+        maybeFailWithConnection {
             it.newBuild().setJavaHome(dummyJdk).run()
         }
 
         then:
-        ex instanceof GradleConnectionException
+        GradleConnectionException ex = thrown()
         ex.cause.message.contains "wrong jdk location"
     }
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
index 84e07c1..4ebad65 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
@@ -118,12 +118,12 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 
     def "gives decent feedback for invalid option"() {
         when:
-        def ex = maybeFailWithConnection { ProjectConnection it ->
+        maybeFailWithConnection { ProjectConnection it ->
             it.newBuild().withArguments('--foreground').run()
         }
 
         then:
-        ex instanceof UnsupportedBuildArgumentException
+        UnsupportedBuildArgumentException ex = thrown()
         ex.message.contains('--foreground')
     }
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
index 253bf03..2178eb2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.integtests.tooling.r11rc1
 
-import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.MavenFileRepository
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
@@ -23,8 +23,8 @@ import org.gradle.tooling.model.ExternalDependency
 import org.gradle.tooling.model.eclipse.EclipseProject
 import org.gradle.tooling.model.idea.IdeaProject
 
- at MinToolingApiVersion('current')
- at MinTargetGradleVersion('current')
+ at MinToolingApiVersion('1.1-rc-2')
+ at MinTargetGradleVersion('1.1-rc-2')
 class DependencyMetaDataCrossVersionSpec extends ToolingApiSpecification {
 
     def "idea libraries contain gradle module information"() {
@@ -54,7 +54,7 @@ class DependencyMetaDataCrossVersionSpec extends ToolingApiSpecification {
 
     private void prepareBuild() {
         def fakeRepo = dist.file("repo")
-        new MavenRepository(fakeRepo).module("foo.bar", "coolLib", 2.0).publish()
+        new MavenFileRepository(fakeRepo).module("foo.bar", "coolLib", 2.0).publish()
 
         dist.file("yetAnotherJar.jar").createFile()
 
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy
deleted file mode 100644
index b9dac68..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF 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.r11rc1
-
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
-import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.tooling.model.internal.migration.ProjectOutput
-import org.gradle.tooling.internal.migration.DefaultArchive
-import org.gradle.tooling.internal.migration.DefaultTestResult
-
-import org.junit.Rule
-
- at MinToolingApiVersion("current")
- at MinTargetGradleVersion("current")
-class MigrationModelCrossVersionSpec extends ToolingApiSpecification {
-    @Rule TestResources resources = new TestResources()
-
-    def "modelContainsAllArchivesOnTheArchivesConfiguration"() {
-        when:
-        def output = withConnection { it.getModel(ProjectOutput.class) }
-
-        then:
-        output instanceof ProjectOutput
-        def archives = output.taskOutputs.findAll { it.getClass().name == DefaultArchive.name } as List
-        archives.size() == 2
-        archives.any { it.file.name.endsWith(".jar") }
-        archives.any { it.file.name.endsWith(".zip") }
-    }
-
-    def "modelContainsAllTestResults"() {
-        when:
-        def output = withConnection { it.getModel(ProjectOutput.class) }
-
-        then:
-        output instanceof ProjectOutput
-        def testResults = output.taskOutputs.findAll { it.getClass().name == DefaultTestResult.name } as List
-        testResults.size() == 2
-        testResults.any { it.xmlReportDir == resources.dir.file("build", "test-results") }
-        testResults.any { it.xmlReportDir == resources.dir.file("build", "other-results") }
-    }
-
-    def "modelContainsAllProjects"() {
-        when:
-        def output = withConnection { it.getModel(ProjectOutput.class) }
-
-        then:
-        output instanceof ProjectOutput
-        output.children.size() == 2
-        output.children.name as Set == ["project1", "project2"] as Set
-        output.children[0].children.empty
-        output.children[1].children.empty
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..e17d52a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/BuildModelCrossVersionSpec.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.r12rc1
+
+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.HierarchicalEclipseProject
+
+ at MinToolingApiVersion("1.2-rc-1")
+ at MinTargetGradleVersion("1.2-rc-1")
+class BuildModelCrossVersionSpec extends ToolingApiSpecification {
+    def "can run tasks before building Eclipse model"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+
+task setup << {
+    println "run"
+    project.description = 'this is a project'
+}
+'''
+
+        when:
+        HierarchicalEclipseProject project = withConnection { ProjectConnection connection ->
+            connection.model(HierarchicalEclipseProject.class).forTasks('setup').get()
+        }
+
+        then:
+        project.description == 'this is a project'
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy
new file mode 100644
index 0000000..7fe0af8
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/ProjectOutcomesModuleCrossVersionSpec.groovy
@@ -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.integtests.tooling.r12rc1
+
+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.internal.outcomes.ProjectOutcomes
+
+ at MinToolingApiVersion("1.2-rc-1")
+ at MinTargetGradleVersion("1.2-rc-1")
+class ProjectOutcomesModuleCrossVersionSpec extends ToolingApiSpecification {
+    def "modelContainsAllArchivesOnTheArchivesConfiguration"() {
+        given:
+        dist.file('build.gradle') << '''
+			apply plugin: "java"
+
+			task zip(type: Zip) {
+    			from jar
+			}
+
+			artifacts {
+    			archives zip
+			}
+		'''
+
+        when:
+        def projectOutcomes = withConnection { ProjectConnection connection ->
+            connection.model(ProjectOutcomes.class).forTasks('assemble').get()
+        }
+
+        then:
+        projectOutcomes instanceof ProjectOutcomes
+        def outcomes = projectOutcomes.outcomes
+        outcomes.size() == 2
+
+        def jar = outcomes.find { it.file.name.endsWith(".jar") }
+        jar
+        jar.taskPath == ':jar'
+        jar.file.file
+        jar.typeIdentifier == 'artifact.jar'
+
+        def zip = outcomes.find { it.file.name.endsWith(".zip") }
+        zip
+        zip.taskPath == ':zip'
+        zip.file.file
+        zip.typeIdentifier == 'artifact.zip'
+    }
+
+    def "modelContainsAllProjects"() {
+        given:
+        dist.file('settings.gradle') << '''
+include 'project1', 'project2'
+'''
+        dist.file('build.gradle') << '''
+apply plugin: 'java'
+'''
+
+        when:
+        buildFile << "apply plugin: 'java'"
+        file("settings.gradle") << "include 'project1', 'project2'"
+
+        def projectOutcomes = withConnection { ProjectConnection connection ->
+            connection.model(ProjectOutcomes.class).forTasks('assemble').get()
+        }
+
+        then:
+        projectOutcomes instanceof ProjectOutcomes
+        projectOutcomes.children.size() == 2
+        projectOutcomes.children.name as Set == ["project1", "project2"] as Set
+        projectOutcomes.children[0].children.empty
+        projectOutcomes.children[1].children.empty
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/UnsupportedOperationFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/UnsupportedOperationFeedbackCrossVersionSpec.groovy
new file mode 100644
index 0000000..01dde7b
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r12rc1/UnsupportedOperationFeedbackCrossVersionSpec.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.integtests.tooling.r12rc1
+
+import org.gradle.integtests.tooling.fixture.MaxTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+ at MinToolingApiVersion("1.2-rc-1")
+ at MaxTargetGradleVersion("1.1")
+class UnsupportedOperationFeedbackCrossVersionSpec extends ToolingApiSpecification {
+    def "fails when attempting to run tasks when building a model"() {
+        when:
+        maybeFailWithConnection { ProjectConnection connection ->
+            connection.model(EclipseProject.class).forTasks('eclipse').get()
+        }
+
+        then:
+        UnsupportedOperationConfigurationException e = thrown()
+        e.message.contains('Unsupported configuration: modelBuilder.forTasks().')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle
deleted file mode 100644
index ed64449..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: "java"
-
-task zip(type: Zip) {
-    from "file.txt"
-}
-
-artifacts {
-    archives zip
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt
deleted file mode 100644
index 6b584e8..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt
+++ /dev/null
@@ -1 +0,0 @@
-content
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java
deleted file mode 100644
index cb98f64..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java
+++ /dev/null
@@ -1,3 +0,0 @@
-public class Person {
-    String name;
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle
deleted file mode 100644
index db76069..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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: "java"
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle
deleted file mode 100644
index f479356..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "project1", "project2"
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle
deleted file mode 100644
index 25222a3..0000000
--- a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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: "java"
-
-task integTest(type: Test) {
-    testResultsDir = file("$buildDir/other-results")
-}
\ No newline at end of file
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 41eeb3e..4b73adb 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
@@ -69,64 +69,79 @@ import java.io.OutputStream;
  * }
  * </pre>
  *
+ * @since 1.0-milestone-3
  */
 public interface BuildLauncher extends LongRunningOperation {
     /**
-     * Sets the tasks to be executed.
+     * Sets the tasks to be executed. If no tasks are specified, the project's default tasks are executed.
      *
      * @param tasks The paths of the tasks to be executed. Relative paths are evaluated relative to the project for which this launcher was created.
      * @return this
+     * @since 1.0-milestone-3
      */
     BuildLauncher forTasks(String... tasks);
 
     /**
-     * Sets the tasks to be executed. Note that the supplied tasks do not necessarily belong to the project which this launcher was created for.
+     * Sets the tasks to be executed. If no tasks are specified, the project's default tasks are executed.
+     *
+     * <p>Note that the supplied tasks do not necessarily need to belong to the project which this launcher was created for.
      *
      * @param tasks The tasks to be executed.
      * @return this
+     * @since 1.0-milestone-3
      */
     BuildLauncher forTasks(Task... tasks);
 
     /**
-     * Sets the tasks to be executed. Note that the supplied tasks do not necessarily belong to the project which this launcher was created for.
+     * Sets the tasks to be executed. If no tasks are specified, the project's default tasks are executed.
+     *
+     * <p>Note that the supplied tasks do not necessarily need to belong to the project which this launcher was created for.
      *
      * @param tasks The tasks to be executed.
      * @return this
+     * @since 1.0-milestone-3
      */
     BuildLauncher forTasks(Iterable<? extends Task> tasks);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-rc-1
      */
     BuildLauncher withArguments(String ... arguments);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     BuildLauncher setStandardOutput(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     BuildLauncher setStandardError(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-7
      */
     BuildLauncher setStandardInput(InputStream inputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-8
      */
     BuildLauncher setJavaHome(File javaHome);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-9
      */
     BuildLauncher setJvmArguments(String... jvmArguments);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     BuildLauncher addProgressListener(ProgressListener listener);
 
@@ -142,6 +157,7 @@ public interface BuildLauncher extends LongRunningOperation {
      * @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.
+     * @since 1.0-milestone-3
      */
     void run() throws GradleConnectionException, UnsupportedBuildArgumentException, IllegalStateException,
             BuildException, UnsupportedVersionException;
@@ -151,6 +167,7 @@ public interface BuildLauncher extends LongRunningOperation {
      *
      * @param handler The handler to supply the result to.
      * @throws IllegalStateException When the connection has been closed or is closing.
+     * @since 1.0-milestone-3
      */
     void run(ResultHandler<? super Void> handler) throws IllegalStateException;
 }
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 316928b..75859f7 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
@@ -48,8 +48,9 @@ import java.net.URI;
  * }
  * </pre>
  *
- * <p>{@code GradleConnector} instances are not thread-safe. If you want to use a {@code GradleConnector} concurrently you *must* always create a
+ * <p>{@code GradleConnector} instances are not thread-safe. If you want to use a {@code GradleConnector} concurrently you <em>must</em> 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>
+ * @since 1.0-milestone-3
  */
 public abstract class GradleConnector {
 
@@ -57,6 +58,7 @@ public abstract class GradleConnector {
      * Creates a new connector instance.
      *
      * @return The instance. Never returns null.
+     * @since 1.0-milestone-3
      */
     public static GradleConnector newConnector() {
         return new ConnectorServices().createConnector();
@@ -68,6 +70,7 @@ public abstract class GradleConnector {
      *
      * @param gradleHome The Gradle installation directory.
      * @return this
+     * @since 1.0-milestone-3
      */
     public abstract GradleConnector useInstallation(File gradleHome);
 
@@ -77,6 +80,7 @@ public abstract class GradleConnector {
      *
      * @param gradleVersion The version to use.
      * @return this
+     * @since 1.0-milestone-3
      */
     public abstract GradleConnector useGradleVersion(String gradleVersion);
 
@@ -86,6 +90,7 @@ public abstract class GradleConnector {
      *
      * @param gradleDistribution The distribution to use.
      * @return this
+     * @since 1.0-milestone-3
      */
     public abstract GradleConnector useDistribution(URI gradleDistribution);
 
@@ -94,6 +99,7 @@ public abstract class GradleConnector {
      *
      * @param projectDir The working directory.
      * @return this
+     * @since 1.0-milestone-3
      */
     public abstract GradleConnector forProjectDirectory(File projectDir);
 
@@ -102,6 +108,7 @@ public abstract class GradleConnector {
      *
      * @param gradleUserHomeDir The user's Gradle home directory to use.
      * @return this
+     * @since 1.0-milestone-3
      */
     public abstract GradleConnector useGradleUserHomeDir(File gradleUserHomeDir);
 
@@ -111,6 +118,7 @@ public abstract class GradleConnector {
      * @return The connection. Never return null.
      * @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.
+     * @since 1.0-milestone-3
      */
     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
index 787f844..7d56519 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
@@ -32,6 +32,7 @@ import java.io.OutputStream;
  * 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.
+ * @since 1.0-milestone-7
  */
 public interface LongRunningOperation {
 
@@ -41,6 +42,7 @@ public interface LongRunningOperation {
      *
      * @param outputStream The output stream.
      * @return this
+     * @since 1.0-milestone-7
      */
     LongRunningOperation setStandardOutput(OutputStream outputStream);
 
@@ -50,6 +52,7 @@ public interface LongRunningOperation {
      *
      * @param outputStream The output stream.
      * @return this
+     * @since 1.0-milestone-7
      */
     LongRunningOperation setStandardError(OutputStream outputStream);
 
@@ -65,7 +68,7 @@ public interface LongRunningOperation {
      *
      * @param inputStream The input stream
      * @return this
-     * @since 1.0-milestone-8
+     * @since 1.0-milestone-7
      */
     LongRunningOperation setStandardInput(InputStream inputStream);
 
@@ -113,14 +116,14 @@ public interface LongRunningOperation {
      * 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.
+     * The command line instructions that are actually separate commands (like '-?' and '-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()}.
+     * If an unknown or unsupported command line option is specified, {@link org.gradle.tooling.exceptions.UnsupportedBuildArgumentException}
+     * will be thrown at the time the operation is executed via {@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
+     * or take a look at the output of the 'gradle -?' command. Supported arguments are those modeled by
      * {@link org.gradle.StartParameter}.
      * <p>
      * The arguments can potentially override some other settings you have configured.
@@ -142,6 +145,7 @@ public interface LongRunningOperation {
      *
      * @param listener The listener
      * @return this
+     * @since 1.0-milestone-7
      */
     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 db39e83..942d571 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,6 +15,7 @@
  */
 package org.gradle.tooling;
 
+import org.gradle.api.Incubating;
 import org.gradle.tooling.model.Model;
 
 import java.io.File;
@@ -64,45 +65,64 @@ import java.io.OutputStream;
  * </pre>
  *
  * @param <T> The type of model to build
+ * @since 1.0-milestone-3
  */
 public interface ModelBuilder<T extends Model> extends LongRunningOperation {
 
     /**
      * {@inheritDoc}
+     * @since 1.0-rc-1
      */
     ModelBuilder<T> withArguments(String ... arguments);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     ModelBuilder<T> setStandardOutput(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     ModelBuilder<T> setStandardError(OutputStream outputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-7
      */
     ModelBuilder<T> setStandardInput(InputStream inputStream);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-8
      */
     ModelBuilder<T> setJavaHome(File javaHome);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-9
      */
     ModelBuilder<T> setJvmArguments(String... jvmArguments);
 
     /**
      * {@inheritDoc}
+     * @since 1.0-milestone-3
      */
     ModelBuilder<T> addProgressListener(ProgressListener listener);
 
     /**
+     * Specifies the tasks to execute before building the model. By default, no tasks are executed.
+     *
+     * @param tasks The paths of the tasks to be executed. Relative paths are evaluated relative to the project for which this launcher was created.
+     * @return this
+     *
+     * @since 1.2
+     */
+    @Incubating
+    ModelBuilder<T> forTasks(String... tasks);
+
+    /**
      * Fetch the model, blocking until it is available.
      *
      * @return The model.
@@ -114,6 +134,7 @@ public interface ModelBuilder<T extends Model> extends LongRunningOperation {
      * @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.
+     * @since 1.0-milestone-3
      */
     T get() throws GradleConnectionException;
 
@@ -122,6 +143,7 @@ public interface ModelBuilder<T extends Model> extends LongRunningOperation {
      *
      * @param handler The handler to supply the result to.
      * @throws IllegalStateException When the connection has been closed or is closing.
+     * @since 1.0-milestone-3
      */
     void get(ResultHandler<? super T> handler) throws IllegalStateException;
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressEvent.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressEvent.java
index 6543f46..2acb4ed 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressEvent.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressEvent.java
@@ -17,12 +17,14 @@ package org.gradle.tooling;
 
 /**
  * Some information about a piece of work of a long running operation.
+ * @since 1.0-milestone-3
  */
 public interface ProgressEvent {
     /**
      * A description of the current piece of work.
      *
      * @return The description.
+     * @since 1.0-milestone-3
      */
     String getDescription();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressListener.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressListener.java
index b6a8d21..ece6372 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressListener.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProgressListener.java
@@ -17,12 +17,14 @@ package org.gradle.tooling;
 
 /**
  * A listener which is notified as some long running operation makes progress.
+ * @since 1.0-milestone-3
  */
 public interface ProgressListener {
     /**
      * Called when the progress status changes.
      *
      * @param event An event describing the status change.
+     * @since 1.0-milestone-3
      */
     void statusChanged(ProgressEvent event);
 }
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 7c60dc3..f4e125f 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
@@ -46,6 +46,7 @@ import org.gradle.tooling.model.Model;
  * <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>
+ * @since 1.0-milestone-3
  */
 public interface ProjectConnection {
     /**
@@ -62,6 +63,7 @@ public interface ProjectConnection {
      * @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.
+     * @since 1.0-milestone-3
      */
     <T extends Model> T getModel(Class<T> viewType) throws UnsupportedVersionException,
             UnknownModelException, BuildException, GradleConnectionException, IllegalStateException;
@@ -75,6 +77,7 @@ public interface ProjectConnection {
      * @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.
+     * @since 1.0-milestone-3
      */
     <T extends Model> void getModel(Class<T> viewType, ResultHandler<? super T> handler) throws IllegalStateException, UnknownModelException;
 
@@ -82,6 +85,7 @@ public interface ProjectConnection {
      * Creates a launcher which can be used to execute a build.
      *
      * @return The launcher.
+     * @since 1.0-milestone-3
      */
     BuildLauncher newBuild();
 
@@ -93,11 +97,13 @@ public interface ProjectConnection {
      * @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.
+     * @since 1.0-milestone-3
      */
     <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.
+     * @since 1.0-milestone-3
      */
     void close();
 }
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 0fa363b..5dec495 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
@@ -19,6 +19,7 @@ package org.gradle.tooling;
  * A handler for an asynchronous operation which returns an object of type T.
  *
  * @param <T> The result type.
+ * @since 1.0-milestone-3
  */
 public interface ResultHandler<T> {
 
@@ -26,6 +27,7 @@ public interface ResultHandler<T> {
      * Handles successful completion of the operation.
      *
      * @param result the result
+     * @since 1.0-milestone-3
      */
     void onComplete(T result);
 
@@ -35,6 +37,7 @@ public interface ResultHandler<T> {
      *  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
+     * @since 1.0-milestone-3
      */
     void onFailure(GradleConnectionException failure);
 }
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 a1b5ac9..012561c 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
@@ -20,7 +20,6 @@ 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;
@@ -28,21 +27,21 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 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) {
         operationParameters = new ConsumerOperationParameters(parameters);
+        operationParameters.setTasks(Collections.<String>emptyList());
         this.connection = connection;
     }
 
     public BuildLauncher forTasks(String... tasks) {
-        this.tasks.clear();
-        this.tasks.addAll(Arrays.asList(tasks));
+        operationParameters.setTasks(Arrays.asList(tasks));
         return this;
     }
 
@@ -52,10 +51,11 @@ class DefaultBuildLauncher implements BuildLauncher {
     }
 
     public BuildLauncher forTasks(Iterable<? extends Task> tasks) {
-        this.tasks.clear();
+        List<String> taskPaths = new ArrayList<String>();
         for (Task task : tasks) {
-            this.tasks.add(task.getPath());
+            taskPaths.add(task.getPath());
         }
+        operationParameters.setTasks(taskPaths);
         return this;
     }
 
@@ -101,17 +101,11 @@ class DefaultBuildLauncher implements BuildLauncher {
     }
 
     public void run(final ResultHandler<? super Void> handler) {
-        connection.executeBuild(new DefaultBuildParameters(), operationParameters, new ResultHandlerAdapter<Void>(handler){
+        connection.run(Void.class, operationParameters, new ResultHandlerAdapter<Void>(handler) {
             @Override
             protected String connectionFailureMessage(Throwable failure) {
                 return String.format("Could not execute build using %s.", connection.getDisplayName());
             }
         });
     }
-
-    private class DefaultBuildParameters implements BuildParametersVersion1 {
-        public List<String> getTasks() {
-            return tasks;
-        }
-    }
 }
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 0de98f1..a4ff873 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
@@ -15,13 +15,10 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.tooling.GradleConnectionException;
-import org.gradle.tooling.ModelBuilder;
-import org.gradle.tooling.ProgressListener;
-import org.gradle.tooling.ResultHandler;
+import org.gradle.tooling.*;
 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.ConsumerPropertyHandler;
 import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
 import org.gradle.tooling.model.Model;
 import org.gradle.tooling.model.UnsupportedMethodException;
@@ -30,6 +27,7 @@ import org.gradle.tooling.model.internal.Exceptions;
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
 
 public class DefaultModelBuilder<T extends Model, P> implements ModelBuilder<T> {
     private final Class<T> modelType;
@@ -54,7 +52,7 @@ public class DefaultModelBuilder<T extends Model, P> implements ModelBuilder<T>
 
     public void get(final ResultHandler<? super T> handler) throws IllegalStateException {
         ResultHandler<P> adaptingHandler = new ProtocolToModelAdaptingHandler(handler);
-        connection.getModel(protocolType, operationParameters, new ResultHandlerAdapter<P>(adaptingHandler) {
+        connection.run(protocolType, operationParameters, new ResultHandlerAdapter<P>(adaptingHandler) {
             @Override
             protected String connectionFailureMessage(Throwable failure) {
                 String message = String.format("Could not fetch model of type '%s' using %s.", modelType.getSimpleName(), connection.getDisplayName());
@@ -102,6 +100,11 @@ public class DefaultModelBuilder<T extends Model, P> implements ModelBuilder<T>
         return this;
     }
 
+    public DefaultModelBuilder<T, P> forTasks(String... tasks) {
+        operationParameters.setTasks(Arrays.asList(tasks));
+        return this;
+    }
+
     private class ProtocolToModelAdaptingHandler implements ResultHandler<P> {
         private final ResultHandler<? super T> handler;
 
@@ -110,8 +113,7 @@ public class DefaultModelBuilder<T extends Model, P> implements ModelBuilder<T>
         }
 
         public void onComplete(P result) {
-            ModelPropertyHandler propertyHandler = new ModelPropertyHandler(connection.getVersionDetails());
-            handler.onComplete(adapter.adapt(modelType, result, propertyHandler));
+            handler.onComplete(adapter.adapt(modelType, result, new ConsumerPropertyHandler(connection.getVersionDetails())));
         }
 
         public void onFailure(GradleConnectionException failure) {
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 bf45a90..50b1dda 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
@@ -129,7 +129,7 @@ public class DistributionFactory {
             progressLogger.setDescription(String.format("Download %s", address));
             progressLogger.started();
             try {
-                new Download().download(address, destination);
+                new Download("Gradle Tooling API", GradleVersion.current().getVersion()).download(address, destination);
             } finally {
                 progressLogger.completed();
             }
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
index 433671f..5b498c8 100644
--- 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
@@ -25,6 +25,7 @@ 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;
+import org.gradle.tooling.model.internal.Exceptions;
 
 /**
  * by Szczepan Faber, created at: 12/21/11
@@ -33,27 +34,47 @@ public class ModelProvider {
 
     public <T> T provide(ConsumerConnection connection, Class<T> type, 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()");
+            }
+        }
+        if (type != Void.class && operationParameters.getTasks() != null) {
+            if (!version.supportsRunningTasksWhenBuildingModel()) {
+                throw Exceptions.unsupportedOperationConfiguration("modelBuilder.forTasks()");
+            }
+        }
+
         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()) {
+        if (version.clientHangsOnEarlyDaemonFailure() && version.isPostM6Model(type)) {
             //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);
-            }
+            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);
+            EclipseProjectVersion3 project = connection.run(EclipseProjectVersion3.class, operationParameters);
             GradleProject gradleProject = new GradleProjectConverter().convert(project);
             return (T) gradleProject;
         }
-        return connection.getModel(type, operationParameters);
+        return connection.run(type, operationParameters);
     }
 }
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
index 09fb6e7..707ff54 100644
--- 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
@@ -17,13 +17,10 @@ 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;
+    <T> void run(Class<T> type, ConsumerOperationParameters operationParameters, ResultHandlerVersion1<? super T> handler) throws UnsupportedOperationException, IllegalStateException;
 
     void stop();
 
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
index 6975409..75c06bd 100644
--- 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
@@ -20,7 +20,6 @@ import org.gradle.internal.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;
@@ -46,19 +45,10 @@ public class DefaultAsyncConnection implements AsyncConnection {
         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 {
+    public <T> void run(final Class<T> type, final ConsumerOperationParameters operationParameters, ResultHandlerVersion1<? super T> handler) throws UnsupportedOperationException, IllegalStateException {
         runLater(handler, new ConnectionAction<T>() {
             public T run() {
-                return connection.getModel(type, operationParameters);
+                return connection.run(type, operationParameters);
             }
         });
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractConsumerConnection.java
new file mode 100644
index 0000000..ff11944
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AbstractConsumerConnection.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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+
+public abstract class AbstractConsumerConnection implements ConsumerConnection {
+    private final ConnectionVersion4 delegate;
+
+    public AbstractConsumerConnection(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());
+    }
+
+    public ConnectionVersion4 getDelegate() {
+        return delegate;
+    }
+
+    public abstract void configure(ConsumerConnectionParameters connectionParameters);
+}
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
index e221fde..a14eb28 100644
--- 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
@@ -16,11 +16,9 @@
 
 package org.gradle.tooling.internal.consumer.connection;
 
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
 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;
 
 /**
@@ -28,43 +26,30 @@ import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
  * <p>
  * by Szczepan Faber, created at: 12/22/11
  */
-public class AdaptedConnection implements ConsumerConnection {
-    private final ConnectionVersion4 delegate;
+public class AdaptedConnection extends AbstractConsumerConnection {
 
     public AdaptedConnection(ConnectionVersion4 delegate) {
-        this.delegate = delegate;
+        super(delegate);
     }
 
-    public void stop() {
-        delegate.stop();
+    public void configure(ConsumerConnectionParameters connectionParameters) {
+        new CompatibleIntrospector(getDelegate()).callSafely("configureLogging", connectionParameters.getVerboseLogging());
     }
 
-    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);
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        if (type.equals(Void.class)) {
+            doRunBuild(operationParameters);
+            return null;
         } else {
-            return (T) delegate.getModel((Class) type, operationParameters);
+            return doGetModel(type, operationParameters);
         }
     }
 
-    public void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters) throws IllegalStateException {
-        delegate.executeBuild(buildParameters, operationParameters);
-    }
-
-    public ConnectionVersion4 getDelegate() {
-        return delegate;
+    protected  <T> T doGetModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+        return (T) getDelegate().getModel((Class) type, operationParameters);
     }
 
-    public void configureLogging(boolean verboseLogging) {
-        new CompatibleIntrospector(delegate).callSafely("configureLogging", verboseLogging);
+    protected void doRunBuild(ConsumerOperationParameters operationParameters) {
+        getDelegate().executeBuild(operationParameters, operationParameters);
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnection.java
new file mode 100644
index 0000000..b14db9e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnection.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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.protocol.BuildActionRunner;
+import org.gradle.tooling.internal.protocol.ConfigurableConnection;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+
+public class BuildActionRunnerBackedConsumerConnection extends AbstractConsumerConnection {
+    private final BuildActionRunner buildActionRunner;
+
+    public BuildActionRunnerBackedConsumerConnection(ConnectionVersion4 delegate) {
+        super(delegate);
+        buildActionRunner = (BuildActionRunner) delegate;
+    }
+
+    public void configure(ConsumerConnectionParameters connectionParameters) {
+        ((ConfigurableConnection) buildActionRunner).configure(connectionParameters);
+    }
+
+    public <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        return buildActionRunner.run(type, operationParameters).getModel();
+    }
+}
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
index f4dda8a..3a7494e 100644
--- 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
@@ -18,7 +18,6 @@ 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
@@ -31,7 +30,5 @@ public interface ConsumerConnection {
     
     VersionDetails getVersionDetails();
 
-    <T> T getModel(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
-
-    void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters) throws IllegalStateException;
+    <T> T run(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnection.java
new file mode 100644
index 0000000..26e7b05
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnection.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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.internal.protocol.InternalConnection;
+
+public class InternalConnectionBackedConsumerConnection extends AdaptedConnection {
+    private final InternalConnection connection;
+
+    public InternalConnectionBackedConsumerConnection(ConnectionVersion4 delegate) {
+        super(delegate);
+        connection = (InternalConnection) delegate;
+    }
+
+    @Override
+    protected <T> T doGetModel(Class<T> type, ConsumerOperationParameters operationParameters) {
+        return connection.getTheModel(type, operationParameters);
+    }
+}
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
index b6945e6..afd99f0 100644
--- 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
@@ -20,10 +20,9 @@ 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.ConsumerConnectionParameters;
 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;
@@ -45,16 +44,15 @@ public class LazyConnection implements ConsumerConnection {
     private boolean stopped;
     private ConsumerConnection connection;
 
-    boolean verboseLogging;
+    ConsumerConnectionParameters connectionParameters;
 
     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;
+        this.connectionParameters = new ConsumerConnectionParameters(verboseLogging);
     }
 
     public void stop() {
@@ -94,20 +92,9 @@ public class LazyConnection implements ConsumerConnection {
         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) {
+    public <T> T run(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);
             }
         });
@@ -132,7 +119,7 @@ public class LazyConnection implements ConsumerConnection {
             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);
+                connection = implementationLoader.create(distribution, loggingProvider.getProgressLoggerFactory(), connectionParameters);
             }
             return connection;
         } finally {
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
index 14aab8d..1090170 100644
--- 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
@@ -19,7 +19,6 @@ 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.
@@ -48,13 +47,8 @@ public class LoggingInitializerConnection implements ConsumerConnection {
         return connection.getVersionDetails();
     }
 
-    public <T> T getModel(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+    public <T> T run(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);
+        return connection.run(type, 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
index d2d084b..c60724d 100644
--- 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
@@ -25,7 +25,6 @@ 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;
 
 /**
@@ -52,19 +51,10 @@ public class ProgressLoggingConnection implements ConsumerConnection {
         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> T run(final Class<T> type, final ConsumerOperationParameters operationParameters) {
+        return run("Build", operationParameters, new BuildAction<T>() {
             public T run(ConsumerConnection connection) {
-                return connection.getModel(type, operationParameters);
+                return connection.run(type, operationParameters);
             }
         });
     }
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
index 28d8100..7f93314 100644
--- 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
@@ -19,6 +19,7 @@ import org.gradle.internal.classpath.ClassPath;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.internal.consumer.Distribution;
 import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -31,12 +32,12 @@ public class CachingToolingImplementationLoader implements ToolingImplementation
         this.loader = loader;
     }
 
-    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
         ClassPath classpath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
 
         ConsumerConnection connection = connections.get(classpath);
         if (connection == null) {
-            connection = loader.create(distribution, progressLoggerFactory, verboseLogging);
+            connection = loader.create(distribution, progressLoggerFactory, connectionParameters);
             connections.put(classpath, 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
index bdd1d6a..2050e04 100644
--- 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
@@ -22,9 +22,14 @@ 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.consumer.connection.*;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
+import org.gradle.tooling.internal.protocol.BuildActionRunner;
 import org.gradle.tooling.internal.protocol.ConnectionVersion4;
-import org.gradle.util.*;
+import org.gradle.tooling.internal.protocol.InternalConnection;
+import org.gradle.util.FilteringClassLoader;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.MutableURLClassLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,7 +48,7 @@ public class DefaultToolingImplementationLoader implements ToolingImplementation
         this.classLoader = classLoader;
     }
 
-    public AdaptedConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
         LOGGER.debug("Using tooling provider from {}", distribution.getDisplayName());
         ClassLoader classLoader = createImplementationClassLoader(distribution, progressLoggerFactory);
         ServiceLocator serviceLocator = new ServiceLocator(classLoader);
@@ -57,9 +62,17 @@ public class DefaultToolingImplementationLoader implements ToolingImplementation
             }
             // 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);
+            AbstractConsumerConnection adaptedConnection;
+            if (connection instanceof BuildActionRunner) {
+                adaptedConnection = new BuildActionRunnerBackedConsumerConnection(connection);
+            } else if (connection instanceof InternalConnection) {
+                adaptedConnection = new InternalConnectionBackedConsumerConnection(connection);
+            } else {
+                adaptedConnection = new AdaptedConnection(connection);
+            }
+            adaptedConnection.configure(connectionParameters);
             return adaptedConnection;
         } catch (UnsupportedVersionException e) {
             throw e;
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
index 9deb8e9..2bbb0fc 100644
--- 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
@@ -20,6 +20,7 @@ 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 org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
 
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -36,10 +37,10 @@ public class SynchronizedToolingImplementationLoader implements ToolingImplement
         this.delegate = delegate;
     }
 
-    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters) {
         if (lock.tryLock()) {
             try {
-                return delegate.create(distribution, progressLoggerFactory, verboseLogging);
+                return delegate.create(distribution, progressLoggerFactory, connectionParameters);
             } finally {
                 lock.unlock();
             }
@@ -49,7 +50,7 @@ public class SynchronizedToolingImplementationLoader implements ToolingImplement
         logger.started();
         lock.lock();
         try {
-            return delegate.create(distribution, progressLoggerFactory, verboseLogging);
+            return delegate.create(distribution, progressLoggerFactory, connectionParameters);
         } 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
index cf5fa51..7389054 100644
--- 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
@@ -18,7 +18,8 @@ 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 org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
 
 public interface ToolingImplementationLoader {
-    ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging);
+    ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, ConsumerConnectionParameters connectionParameters);
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerConnectionParameters.java
new file mode 100644
index 0000000..3138a48
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerConnectionParameters.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.tooling.internal.consumer.parameters;
+
+import org.gradle.tooling.internal.protocol.ConnectionParameters;
+import org.gradle.util.GradleVersion;
+
+public class ConsumerConnectionParameters implements ConnectionParameters {
+    private final boolean verboseLogging;
+
+    public ConsumerConnectionParameters(boolean verboseLogging) {
+        this.verboseLogging = verboseLogging;
+    }
+
+    public boolean getVerboseLogging() {
+        return verboseLogging;
+    }
+
+    public String getConsumerVersion() {
+        return GradleVersion.current().getVersion();
+    }
+}
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
index 2bfb28f..f3c84ad 100644
--- 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
@@ -19,6 +19,8 @@ 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.BuildParameters;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
 
 import java.io.File;
@@ -31,7 +33,7 @@ import java.util.concurrent.TimeUnit;
 /**
  * by Szczepan Faber, created at: 1/9/12
  */
-public class ConsumerOperationParameters implements BuildOperationParametersVersion1 {
+public class ConsumerOperationParameters implements BuildOperationParametersVersion1, BuildParametersVersion1, BuildParameters {
 
     private final ProgressListenerAdapter progressListener = new ProgressListenerAdapter();
     private final ConnectionParameters parameters;
@@ -44,6 +46,7 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
     private File javaHome;
     private List<String> jvmArguments;
     private List<String> arguments;
+    private List<String> tasks;
 
     public ConsumerOperationParameters(ConnectionParameters parameters) {
         this.parameters = parameters;
@@ -150,4 +153,12 @@ public class ConsumerOperationParameters implements BuildOperationParametersVers
     public List<String> getArguments() {
         return arguments;
     }
+
+    public List<String> getTasks() {
+        return tasks;
+    }
+
+    public void setTasks(List<String> tasks) {
+        this.tasks = tasks;
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ConsumerPropertyHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ConsumerPropertyHandler.java
new file mode 100644
index 0000000..996ac73
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ConsumerPropertyHandler.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.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.protocol.eclipse.EclipseProjectVersion3;
+
+/**
+ * by Szczepan Faber, created at: 4/2/12
+ */
+public class ConsumerPropertyHandler implements MethodInvoker {
+
+    private final VersionDetails versionDetails;
+
+    public ConsumerPropertyHandler(VersionDetails versionDetails) {
+        this.versionDetails = versionDetails;
+    }
+
+    public void invoke(MethodInvocation invocation) throws Throwable {
+        if (invocation.getName().equals("getGradleProject")
+                && invocation.getDelegate() instanceof EclipseProjectVersion3
+                && !versionDetails.supportsGradleProjectModel()) {
+            invocation.setResult(new GradleProjectConverter().convert((EclipseProjectVersion3) invocation.getDelegate()));
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvocation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvocation.java
new file mode 100644
index 0000000..c223d38
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvocation.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.tooling.internal.consumer.protocoladapter;
+
+import java.lang.reflect.Type;
+
+public class MethodInvocation {
+    private final Object[] parameters;
+    private final Class returnType;
+    private final Type genericReturnType;
+    private final String name;
+    private final Class<?>[] parameterTypes;
+    private Object result;
+    private boolean found;
+    private Object delegate;
+
+    MethodInvocation(String name, Class returnType, Type genericReturnType, Class<?>[] parameterTypes, Object delegate, Object[] parameters) {
+        this.name = name;
+        this.returnType = returnType;
+        this.genericReturnType = genericReturnType;
+        this.parameterTypes = parameterTypes;
+        this.delegate = delegate;
+        this.parameters = parameters;
+    }
+
+    public Object[] getParameters() {
+        return parameters;
+    }
+
+    public Class getReturnType() {
+        return returnType;
+    }
+
+    public Type getGenericReturnType() {
+        return genericReturnType;
+    }
+
+    public void setResult(Object result) {
+        found = true;
+        this.result = result;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Class<?>[] getParameterTypes() {
+        return parameterTypes;
+    }
+
+    public boolean found() {
+        return found;
+    }
+
+    public Object getDelegate() {
+        return delegate;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvoker.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvoker.java
new file mode 100644
index 0000000..2e4a473
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/MethodInvoker.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.tooling.internal.consumer.protocoladapter;
+
+public interface MethodInvoker {
+    void invoke(MethodInvocation invocation) throws Throwable;
+}
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
deleted file mode 100644
index 7723529..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ModelPropertyHandler.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
index c71cbad..2428939 100644
--- 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
@@ -16,37 +16,69 @@
 package org.gradle.tooling.internal.consumer.protocoladapter;
 
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.reflect.DirectInstantiator;
 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.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
+/**
+ * Adapts some source object to some target type.
+ */
 public class ProtocolToModelAdapter {
-
+    private static final MethodInvoker NO_OP_HANDLER = new MethodInvoker() {
+        public void invoke(MethodInvocation invocation) throws Throwable {
+        }
+    };
+    private static final Object[] EMPTY = new Object[0];
+    private static final Pattern IS_SUPPORT_METHOD = Pattern.compile("is(\\w+)Supported");
+    private static final Pattern GETTER_METHOD = Pattern.compile("get(\\w+)");
+    private static final Pattern IS_METHOD = Pattern.compile("is(\\w+)");
     private final TargetTypeProvider targetTypeProvider = new TargetTypeProvider();
 
-    public <T, S> T adapt(Class<T> targetType, S protocolObject, ModelPropertyHandler modelPropertyHandler) {
+    public <T, S> T adapt(Class<T> targetType, S protocolObject) {
+        return adapt(targetType, protocolObject, NO_OP_HANDLER);
+    }
+
+    public <T, S> T adapt(Class<T> targetType, S protocolObject, MethodInvoker overrideMethodInvoker) {
         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));
+        Object proxy = Proxy.newProxyInstance(target.getClassLoader(), new Class<?>[]{target}, new InvocationHandlerImpl(protocolObject, overrideMethodInvoker));
         return target.cast(proxy);
     }
 
+    /**
+     * Adapts the source object.
+     *
+     * @param mixInClass A bean that provides implementations for methods of the target type. If this bean implements the given method, it is preferred over the source object's implementation.
+     */
+    public <T, S> T adapt(Class<T> targetType, S protocolObject, Class<?> mixInClass) {
+        MixInMethodInvoker mixInMethodInvoker = new MixInMethodInvoker(mixInClass, new ReflectionMethodInvoker(NO_OP_HANDLER));
+        T proxy = adapt(targetType, protocolObject, mixInMethodInvoker);
+        mixInMethodInvoker.setProxy(proxy);
+        return 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;
+        private final MethodInvoker invoker;
 
-        public InvocationHandlerImpl(Object delegate, ModelPropertyHandler modelPropertyHandler) {
+        public InvocationHandlerImpl(Object delegate, MethodInvoker overrideMethodInvoker) {
             this.delegate = delegate;
-            this.modelPropertyHandler = modelPropertyHandler;
+            invoker = new SupportedPropertyInvoker(
+                    new SafeMethodInvoker(
+                            new PropertyCachingMethodInvoker(
+                                    new ChainedMethodInvoker(
+                                            overrideMethodInvoker,
+                                            new ReflectionMethodInvoker(overrideMethodInvoker)))));
             try {
                 equalsMethod = Object.class.getMethod("equals", Object.class);
                 hashCodeMethod = Object.class.getMethod("hashCode");
@@ -85,67 +117,82 @@ public class ProtocolToModelAdapter {
                 return hashCode();
             }
 
-            if (method.getName().matches("get\\w+")) {
-                if (properties.containsKey(method.getName())) {
-                    return properties.get(method.getName());
-                }
+            MethodInvocation invocation = new MethodInvocation(method.getName(), method.getReturnType(), method.getGenericReturnType(), method.getParameterTypes(), delegate, params);
+            invoker.invoke(invocation);
+            if (!invocation.found()) {
+                String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()";
+                throw Exceptions.unsupportedMethod(methodName);
+            }
+            return invocation.getResult();
+        }
+    }
 
-                Object value;
-                if (modelPropertyHandler.shouldHandle(method, delegate)) {
-                    value = modelPropertyHandler.getPropertyValue(method, delegate);
-                } else {
-                    value = doInvokeMethod(method, params);
-                }
-                properties.put(method.getName(), value);
-                return value;
+    private static class ChainedMethodInvoker implements MethodInvoker {
+        private final MethodInvoker[] invokers;
+
+        private ChainedMethodInvoker(MethodInvoker... invokers) {
+            this.invokers = invokers;
+        }
+
+        public void invoke(MethodInvocation method) throws Throwable {
+            for (int i = 0; !method.found() && i < invokers.length; i++) {
+                MethodInvoker invoker = invokers[i];
+                invoker.invoke(method);
             }
+        }
+    }
 
-            return doInvokeMethod(method, params);
+    private class ReflectionMethodInvoker implements MethodInvoker {
+        private final MethodInvoker override;
+
+        private ReflectionMethodInvoker(MethodInvoker override) {
+            this.override = override;
         }
 
-        private Object doInvokeMethod(Method method, Object[] params) throws Throwable {
-            Method targetMethod = methods.get(method);
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            // TODO - cache method lookup
+            Method targetMethod = locateMethod(invocation);
             if (targetMethod == null) {
-                targetMethod = findMethod(method);
-                methods.put(method, targetMethod);
+                return;
             }
 
             Object returnValue;
             try {
-                returnValue = targetMethod.invoke(delegate, params);
+                returnValue = targetMethod.invoke(invocation.getDelegate(), invocation.getParameters());
             } catch (InvocationTargetException e) {
                 throw e.getCause();
             }
 
-            if (returnValue == null || method.getReturnType().isInstance(returnValue)) {
-                return returnValue;
+            if (returnValue == null || invocation.getReturnType().isInstance(returnValue)) {
+                invocation.setResult(returnValue);
+                return;
             }
 
-            return convert(returnValue, method.getGenericReturnType());
+            invocation.setResult(convert(returnValue, invocation.getGenericReturnType()));
         }
 
-        private Method findMethod(Method method) {
+        private Method locateMethod(MethodInvocation invocation) {
+            Class<?> sourceClass = invocation.getDelegate().getClass();
             Method match;
             try {
-                match = delegate.getClass().getMethod(method.getName(), method.getParameterTypes());
+                match = sourceClass.getMethod(invocation.getName(), invocation.getParameterTypes());
             } catch (NoSuchMethodException e) {
-                String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()";
-                throw Exceptions.unsupportedMethod(methodName, e);
+                return null;
             }
 
             LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
-            queue.add(delegate.getClass());
+            queue.add(sourceClass);
             while (!queue.isEmpty()) {
                 Class<?> c = queue.removeFirst();
                 try {
-                    match = c.getMethod(method.getName(), method.getParameterTypes());
+                    match = c.getMethod(invocation.getName(), invocation.getParameterTypes());
                 } catch (NoSuchMethodException e) {
                     // ignore
                 }
                 for (Class<?> interfaceType : c.getInterfaces()) {
                     queue.addFirst(interfaceType);
                 }
-                if (c.getSuperclass() !=null) {
+                if (c.getSuperclass() != null) {
                     queue.addFirst(c.getSuperclass());
                 }
             }
@@ -169,7 +216,7 @@ public class ProtocolToModelAdapter {
                 if (((Class) targetType).isPrimitive()) {
                     return value;
                 }
-                return adapt((Class) targetType, value, modelPropertyHandler);
+                return adapt((Class) targetType, value, override);
             }
             throw new UnsupportedOperationException(String.format("Cannot convert object of %s to %s.", value.getClass(), targetType));
         }
@@ -183,4 +230,129 @@ public class ProtocolToModelAdapter {
             return elementType;
         }
     }
+
+    private static class PropertyCachingMethodInvoker implements MethodInvoker {
+        private final Map<String, Object> properties = new HashMap<String, Object>();
+        private final Set<String> unknown = new HashSet<String>();
+        private final MethodInvoker next;
+
+        private PropertyCachingMethodInvoker(MethodInvoker next) {
+            this.next = next;
+        }
+
+        public void invoke(MethodInvocation method) throws Throwable {
+            if ((GETTER_METHOD.matcher(method.getName()).matches() || IS_METHOD.matcher(method.getName()).matches()) && method.getParameterTypes().length == 0) {
+                if (properties.containsKey(method.getName())) {
+                    method.setResult(properties.get(method.getName()));
+                    return;
+                }
+                if (unknown.contains(method.getName())) {
+                    return;
+                }
+
+                Object value;
+                next.invoke(method);
+                if (!method.found()) {
+                    unknown.add(method.getName());
+                    return;
+                }
+                value = method.getResult();
+                properties.put(method.getName(), value);
+                return;
+            }
+
+            next.invoke(method);
+        }
+    }
+
+    private static class SafeMethodInvoker implements MethodInvoker {
+        private final MethodInvoker next;
+
+        private SafeMethodInvoker(MethodInvoker next) {
+            this.next = next;
+        }
+
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            next.invoke(invocation);
+            if (invocation.found()) {
+                return;
+            }
+
+            boolean getter = GETTER_METHOD.matcher(invocation.getName()).matches();
+            if (!getter || invocation.getParameterTypes().length != 1) {
+                return;
+            }
+
+            MethodInvocation getterInvocation = new MethodInvocation(invocation.getName(), invocation.getReturnType(), invocation.getGenericReturnType(), new Class[0], invocation.getDelegate(), EMPTY);
+            next.invoke(getterInvocation);
+            if (getterInvocation.found() && getterInvocation.getResult() != null) {
+                invocation.setResult(getterInvocation.getResult());
+            } else {
+                invocation.setResult(invocation.getParameters()[0]);
+            }
+        }
+    }
+
+    private static class SupportedPropertyInvoker implements MethodInvoker {
+        private final MethodInvoker next;
+
+        private SupportedPropertyInvoker(MethodInvoker next) {
+            this.next = next;
+        }
+
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            Matcher matcher = IS_SUPPORT_METHOD.matcher(invocation.getName());
+            if (!matcher.matches()) {
+                next.invoke(invocation);
+                return;
+            }
+
+            String getterName = String.format("get%s", matcher.group(1));
+            MethodInvocation getterInvocation = new MethodInvocation(getterName, invocation.getReturnType(), invocation.getGenericReturnType(), new Class[0], invocation.getDelegate(), EMPTY);
+            next.invoke(getterInvocation);
+            invocation.setResult(getterInvocation.found());
+        }
+    }
+
+    private static class MixInMethodInvoker implements MethodInvoker {
+        private Object proxy;
+        private Object instance;
+        private final Class<?> mixInClass;
+        private final MethodInvoker next;
+        private final ThreadLocal<MethodInvocation> current = new ThreadLocal<MethodInvocation>();
+
+        public MixInMethodInvoker(Class<?> mixInClass, MethodInvoker next) {
+            this.mixInClass = mixInClass;
+            this.next = next;
+        }
+
+        public void invoke(MethodInvocation invocation) throws Throwable {
+            if (current.get() != null) {
+                // Already invoking a method on the mix-in
+                return;
+            }
+
+            if (instance == null) {
+                instance = new DirectInstantiator().newInstance(mixInClass, proxy);
+            }
+            MethodInvocation beanInvocation = new MethodInvocation(invocation.getName(), invocation.getReturnType(), invocation.getGenericReturnType(), invocation.getParameterTypes(), instance, invocation.getParameters());
+            current.set(beanInvocation);
+            try {
+                next.invoke(beanInvocation);
+            } finally {
+                current.set(null);
+            }
+            if (beanInvocation.found()) {
+                invocation.setResult(beanInvocation.getResult());
+            }
+        }
+
+        public void setProxy(Object proxy) {
+            this.proxy = proxy;
+        }
+
+        public Object getProxy() {
+            return proxy;
+        }
+    }
 }
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
index cd8899d..95e5be6 100644
--- 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
@@ -18,6 +18,7 @@ package org.gradle.tooling.internal.consumer.protocoladapter;
 
 import org.gradle.tooling.model.idea.IdeaModuleDependency;
 import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -32,6 +33,7 @@ public class TargetTypeProvider {
     public TargetTypeProvider() {
         configuredTargetTypes.put(IdeaSingleEntryLibraryDependency.class.getCanonicalName(), IdeaSingleEntryLibraryDependency.class);
         configuredTargetTypes.put(IdeaModuleDependency.class.getCanonicalName(), IdeaModuleDependency.class);
+        configuredTargetTypes.put(GradleFileBuildOutcome.class.getCanonicalName(), GradleFileBuildOutcome.class);
     }
 
     /**
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
deleted file mode 100644
index c5183d1..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/FeatureValidator.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.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
index 46078bc..024cc26 100644
--- 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
@@ -26,7 +26,7 @@ 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 org.gradle.tooling.model.internal.migration.ProjectOutput;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -57,7 +57,7 @@ public class ModelMapping {
         Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
         map.put(BuildEnvironment.class, InternalBuildEnvironment.class);
         map.put(TestModel.class, InternalTestModel.class);
-        map.put(ProjectOutput.class, InternalProjectOutput.class);
+        map.put(ProjectOutcomes.class, InternalProjectOutcomes.class);
         return map;
     }
 
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
index 7124627..9c644ff 100644
--- 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
@@ -27,11 +27,16 @@ public class VersionDetails {
     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");
+    private static final GradleVersion V1_1 = GradleVersion.version("1.1");
 
     public VersionDetails(String version) {
         gradleVersion = GradleVersion.version(version);
     }
 
+    public String getVersion() {
+        return gradleVersion.getVersion();
+    }
+
     public boolean supportsCompleteBuildEnvironment() {
         return gradleVersion.compareTo(M7) > 0;
     }
@@ -40,8 +45,8 @@ public class VersionDetails {
         return gradleVersion.equals(M5) || gradleVersion.equals(M6);
     }
 
-    public <T> boolean isPostM6Model(Class<T> internalModelType) {
-        return !ModelMapping.getModelsUpToM6().containsValue(internalModelType);
+    public boolean isPostM6Model(Class<?> internalModelType) {
+        return !ModelMapping.getModelsUpToM6().containsValue(internalModelType) && internalModelType != Void.class;
     }
 
     public boolean supportsConfiguringJavaHome() {
@@ -56,8 +61,8 @@ public class VersionDetails {
         return gradleVersion.compareTo(M7) > 0;
     }
 
-    public String getVersion() {
-        return gradleVersion.getVersion();
+    public boolean supportsRunningTasksWhenBuildingModel() {
+        return gradleVersion.compareTo(V1_1) > 0;
     }
 
     public boolean supportsGradleProjectModel() {
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
index e774bea..02221f9 100644
--- 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
@@ -84,7 +84,7 @@ public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryL
         return this;
     }
 
-    public DefaultIdeaSingleEntryLibraryDependency setExternalGradleModule(GradleModuleVersion moduleVersion) {
+    public DefaultIdeaSingleEntryLibraryDependency setGradleModuleVersion(GradleModuleVersion moduleVersion) {
         this.moduleVersion = moduleVersion;
         return this;
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java
deleted file mode 100644
index 2e93086..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import org.gradle.tooling.model.internal.migration.Archive;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultArchive implements Archive, Serializable {
-    private final File file;
-
-    public DefaultArchive(File file) {
-        this.file = file;
-    }
-
-    public File getFile() {
-        return file;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java
deleted file mode 100644
index b3a1b55..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import com.google.common.collect.Lists;
-
-import org.gradle.tooling.internal.protocol.InternalProjectOutput;
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
-import org.gradle.tooling.model.internal.migration.ProjectOutput;
-import org.gradle.tooling.model.internal.migration.TaskOutput;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.List;
-import java.util.Set;
-
-public class DefaultProjectOutput implements InternalProjectOutput, ProjectOutput, Serializable {
-    private final String name;
-    private final String path;
-    private final String description;
-    private final File projectDirectory;
-    private final Set<TaskOutput> taskOutputs;
-    private final ProjectOutput parent;
-    private final List<ProjectOutput> children = Lists.newArrayList();
-
-    public DefaultProjectOutput(String name, String path, String description, File projectDirectory, Set<TaskOutput> taskOutputs, ProjectOutput parent) {
-        this.name = name;
-        this.path = path;
-        this.description = description;
-        this.projectDirectory = projectDirectory;
-        this.taskOutputs = taskOutputs;
-        this.parent = parent;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public File getProjectDirectory() {
-        return projectDirectory;
-    }
-
-    public Set<TaskOutput> getTaskOutputs() {
-        return taskOutputs;
-    }
-
-    public ProjectOutput getParent() {
-        return parent;
-    }
-
-    public DomainObjectSet<ProjectOutput> getChildren() {
-        return new ImmutableDomainObjectSet<ProjectOutput>(children);
-    }
-
-    public void addChild(ProjectOutput child) {
-        children.add(child);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java
deleted file mode 100644
index b1f7ad0..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import org.gradle.tooling.model.internal.migration.TestResult;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultTestResult implements TestResult, Serializable {
-    private final File xmlReportDir;
-
-    public DefaultTestResult(File xmlReportDir) {
-        this.xmlReportDir = xmlReportDir;
-    }
-
-    public File getXmlReportDir() {
-        return xmlReportDir;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.java
new file mode 100644
index 0000000..6bebd3d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleBuildOutcome.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.tooling.internal.outcomes;
+
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome;
+
+import java.io.Serializable;
+
+public class DefaultGradleBuildOutcome implements GradleBuildOutcome, Serializable {
+
+    private final String id;
+    private final String description;
+    private final String taskPath;
+
+    public DefaultGradleBuildOutcome(String id, String description, String taskPath) {
+        this.id = id;
+        this.description = description;
+        this.taskPath = taskPath;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getTaskPath() {
+        return taskPath;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleFileBuildOutcome.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleFileBuildOutcome.java
new file mode 100644
index 0000000..64f480a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultGradleFileBuildOutcome.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.tooling.internal.outcomes;
+
+import org.gradle.tooling.model.internal.outcomes.GradleFileBuildOutcome;
+
+import java.io.File;
+
+public class DefaultGradleFileBuildOutcome extends DefaultGradleBuildOutcome implements GradleFileBuildOutcome {
+
+    private final File file;
+    private final String typeIdentifier;
+
+    public DefaultGradleFileBuildOutcome(String id, String description, String taskPath, File file, String typeIdentifier) {
+        super(id, description, taskPath);
+        this.file = file;
+        this.typeIdentifier = typeIdentifier;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getTypeIdentifier() {
+        return typeIdentifier;
+    }
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultProjectOutcomes.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultProjectOutcomes.java
new file mode 100644
index 0000000..4e27335
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/outcomes/DefaultProjectOutcomes.java
@@ -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.tooling.internal.outcomes;
+
+import com.google.common.collect.Lists;
+import org.gradle.tooling.internal.protocol.InternalProjectOutcomes;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+import org.gradle.tooling.model.internal.outcomes.GradleBuildOutcome;
+import org.gradle.tooling.model.internal.outcomes.ProjectOutcomes;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+
+public class DefaultProjectOutcomes implements InternalProjectOutcomes, ProjectOutcomes, Serializable {
+    private final String name;
+    private final String projectPath;
+    private final String description;
+    private final File projectDirectory;
+    private final DomainObjectSet<? extends GradleBuildOutcome> outcomes;
+    private final ProjectOutcomes parent;
+    private final List<ProjectOutcomes> children = Lists.newArrayList();
+
+    public DefaultProjectOutcomes(String name, String projectPath, String description, File projectDirectory,
+                                  DomainObjectSet<? extends GradleBuildOutcome> outcomes, ProjectOutcomes parent) {
+        this.name = name;
+        this.projectPath = projectPath;
+        this.description = description;
+        this.projectDirectory = projectDirectory;
+        this.outcomes = outcomes;
+        this.parent = parent;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getPath() {
+        return projectPath;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public File getProjectDirectory() {
+        return projectDirectory;
+    }
+
+    public DomainObjectSet<? extends GradleBuildOutcome> getOutcomes() {
+        return outcomes;
+    }
+
+    public ProjectOutcomes getParent() {
+        return parent;
+    }
+
+    public DomainObjectSet<ProjectOutcomes> getChildren() {
+        return new ImmutableDomainObjectSet<ProjectOutcomes>(children);
+    }
+
+    public void addChild(ProjectOutcomes child) {
+        children.add(child);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildActionRunner.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildActionRunner.java
new file mode 100644
index 0000000..984f810
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildActionRunner.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.tooling.internal.protocol;
+
+/**
+ * Mixed into a provider connection, to run actions against a build.
+ *
+ * @since 1.2-rc-1
+ */
+public interface BuildActionRunner extends InternalProtocolInterface {
+    /**
+     * Performs some action against a build and returns some result of the given type.
+     *
+     * @param type The desired result type. Use {@link Void} to indicate that no result is desired.
+     * @throws UnsupportedOperationException When the given model type is not supported.
+     * @throws IllegalStateException When this connection has been stopped.
+     */
+    <T> BuildResult<T> run(Class<T> type, BuildParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParameters.java
new file mode 100644
index 0000000..8b90eb9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildParameters.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.tooling.internal.protocol;
+
+/**
+ * The parameters for running a build.
+ *
+ * <p>This is a marker interface. Instances are queried dynamically to see which parameters they support. See {@code ProviderOperationParameters} for details of the methods that provider expects,
+ * and {@code ConsumerOperationParameters} for details of what the consumer currently provides.
+ *
+ * @since 1.2-rc-1
+ */
+public interface BuildParameters extends InternalProtocolInterface {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildResult.java
new file mode 100644
index 0000000..1107249
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildResult.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.tooling.internal.protocol;
+
+/**
+ * The result of running a build.
+ *
+ * <p>This is a mostly-empty interface. Instances are queried dynamically to see which properties they support.
+ * See {@code ProviderBuildResult} for details on properties supported by the provider.
+ *
+ * @since 1.2-rc-1
+ */
+public interface BuildResult<T> extends InternalProtocolInterface {
+    T getModel();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConfigurableConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConfigurableConnection.java
new file mode 100644
index 0000000..f99ddb1
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConfigurableConnection.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.tooling.internal.protocol;
+
+/**
+ * Mixed into a provider connection, to allow the connection to be configured.
+ *
+ * @since 1.2-rc-1
+ */
+public interface ConfigurableConnection extends InternalProtocolInterface {
+    void configure(ConnectionParameters parameters);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionParameters.java
new file mode 100644
index 0000000..7e24dcf
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionParameters.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.tooling.internal.protocol;
+
+/**
+ * Initial configuration for a provider connection.
+ *
+ * <p>This is a mostly-empty interface. Instances are queried dynamically to see which properties they support. See {@code ProviderConnectionParameters} to see the configuration expected by the provider,
+ * and {@link org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters} to see the configuration provided by the consumer.
+ *
+ * @since 1.2-rc-1
+ */
+public interface ConnectionParameters extends InternalProtocolInterface {
+    String getConsumerVersion();
+}
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 67d89bb..38e164b 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
@@ -18,7 +18,15 @@ package org.gradle.tooling.internal.protocol;
 /**
  * <p>Represents a connection to a particular Gradle implementation.
  *
- * <p>Implementations must be thread-safe.
+ * <p>The following constraints apply to implementations:
+ * <ul>
+ * <li>Implementations must be thread-safe.
+ * <li>Implementations should implement {@link BuildActionRunner}.
+ * <li>Implementations should implement {@link ConfigurableConnection}.
+ * <li>Implementations should provide a zero-args constructor
+ * <li>For backwards compatibility, implementations should implement {@link InternalConnection}.
+ * <li>For backwards compatibility, implementations should provide a {@code void configureLogging(boolean verboseLogging)} method.
+ * </ul>
  *
  * <p>
  * Changes to this interface may break the cross-version protocol.
@@ -39,10 +47,10 @@ 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 Use {@link BuildActionRunner#run(Class, BuildParameters)} instead.
      */
     @Deprecated
     ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException;
@@ -52,6 +60,8 @@ public interface ConnectionVersion4 {
      *
      * @param buildParameters The parameters for the build.
      * @throws IllegalStateException When this connection has been stopped.
+     * @deprecated Use {@link BuildActionRunner#run(Class, BuildParameters)} instead.
      */
+    @Deprecated
     void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) throws IllegalStateException;
 }
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
index 1040363..b716e72 100644
--- 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
@@ -18,9 +18,11 @@ package org.gradle.tooling.internal.protocol;
 
 /**
  * by Szczepan Faber, created at: 1/1/12
+ *
+ * @deprecated Use {@link BuildActionRunner} instead.
  */
+ at Deprecated
 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.
@@ -29,6 +31,8 @@ public interface InternalConnection extends ConnectionVersion4, InternalProtocol
      *
      * @throws UnsupportedOperationException When the given model type is not supported.
      * @throws IllegalStateException When this connection has been stopped.
+     * @deprecated Use {@link BuildActionRunner#run(Class, BuildOperationParametersVersion1)} instead.
      */
+    @Deprecated
     <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/InternalProjectOutcomes.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutcomes.java
new file mode 100644
index 0000000..dd2172f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutcomes.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.tooling.internal.protocol;
+
+public interface InternalProjectOutcomes extends ProjectVersion3, InternalProtocolInterface {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java
deleted file mode 100644
index 79bf15b..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-public interface InternalProjectOutput extends ProjectVersion3, InternalProtocolInterface {
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
index 7e68178..49afadb 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.tooling.model;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 import org.gradle.api.Nullable;
 
 import java.io.File;
@@ -56,6 +56,6 @@ public interface ExternalDependency extends Dependency {
      * @since 1.1-rc-1
      */
     @Nullable
-    @Experimental
+    @Incubating
     GradleModuleVersion getGradleModuleVersion();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java
index 1c39d9d..4a79ad0 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java
@@ -16,14 +16,14 @@
 
 package org.gradle.tooling.model;
 
-import org.gradle.api.Experimental;
+import org.gradle.api.Incubating;
 
 /**
  * Informs about a module version, i.e. group, name, version.
  *
  * @since 1.1-rc-1
  */
- at Experimental
+ at Incubating
 public interface GradleModuleVersion {
 
     /**
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 2c91289..02ddddb 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
@@ -49,5 +49,4 @@ public interface Task {
      * @return The element which this task belongs to.
      */
     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/internal/Exceptions.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java
index 8c64bae..85409dd 100644
--- 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
@@ -28,10 +28,6 @@ public class Exceptions {
             "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));
     }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java
deleted file mode 100644
index c383a10..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import java.io.File;
-
-/**
- * An archive produced by the build.
- */
-public interface Archive extends TaskOutput {
-    File getFile();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java
deleted file mode 100644
index 1bb9fdb..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.HierarchicalElement;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * The outputs produced by a Gradle project.
- */
-public interface ProjectOutput extends HierarchicalElement {
-    ProjectOutput getParent();
-    DomainObjectSet<ProjectOutput> getChildren();
-    String getPath();
-    File getProjectDirectory();
-    Set<TaskOutput> getTaskOutputs();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java
deleted file mode 100644
index b250abf..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import org.gradle.tooling.model.Model;
-
-/**
- * An output produced by a Gradle task.
- */
-public interface TaskOutput extends Model {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java
deleted file mode 100644
index 94b2a3e..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.migration;
-
-import java.io.File;
-
-/**
- * The results from a test run.
- */
-public interface TestResult extends TaskOutput {
-    File getXmlReportDir();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleBuildOutcome.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleBuildOutcome.java
new file mode 100644
index 0000000..e493615
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleBuildOutcome.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.tooling.model.internal.outcomes;
+
+import org.gradle.api.Nullable;
+
+/**
+ * Represents something that happens as part of a build.
+ *
+ * @since 1.2
+ */
+public interface GradleBuildOutcome {
+
+    /**
+     * An internal unique identifier for this outcome.
+     *
+     * The identifier must be unique for an outcome within the project that it is part of. Identifiers should
+     * be deterministic, in that a build executed the same way twice should produce the same set of outcome IDs.
+     *
+     * The value of the id does not need to be meaningful to a user.
+     *
+     * @return The id of this outcome. Never null.
+     */
+    String getId();
+
+    /**
+     * A textual description of the outcome.
+     *
+     * @return The description. Never null.
+     */
+    String getDescription();
+
+    /**
+     * The path to the task that created the outcome, if it was known to be created by a task.
+     *
+     * @return The path to the task the “created” the outcome, or {@code null} if it was not produced by a task.
+     */
+    @Nullable
+    String getTaskPath();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleFileBuildOutcome.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleFileBuildOutcome.java
new file mode 100644
index 0000000..96fd135
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/GradleFileBuildOutcome.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.model.internal.outcomes;
+
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+/**
+ * A build outcome, that is represented as a file.
+ *
+ * @since 1.2
+ */
+public interface GradleFileBuildOutcome extends GradleBuildOutcome {
+
+    /**
+     * The file, were it was produced by the build.
+     *
+     * May be a file or a directory.
+     *
+     * @return The file.
+     */
+    File getFile();
+
+    /**
+     * A canonical identifier for what the outcome is.
+     *
+     * The string is free form, but there is expected to be agreement between the
+     * server and client about the values. For example, the {@link #getFile()} may have any file extension
+     * while the “type identifier” is “zip” to convey that it is a zip file.
+     *
+     * If the value is {@code null}, A type identifier could not be determined.
+     *
+     * @return The unique type identifier for the file.
+     */
+    @Nullable
+    String getTypeIdentifier();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/ProjectOutcomes.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/ProjectOutcomes.java
new file mode 100644
index 0000000..16bd2b7
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/outcomes/ProjectOutcomes.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.tooling.model.internal.outcomes;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.HierarchicalElement;
+
+import java.io.File;
+
+/**
+ * The outputs produced by a Gradle project.
+ */
+public interface ProjectOutcomes extends HierarchicalElement {
+    ProjectOutcomes getParent();
+    DomainObjectSet<ProjectOutcomes> getChildren();
+    String getPath();
+    File getProjectDirectory();
+    DomainObjectSet<? extends GradleBuildOutcome> getOutcomes();
+}
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 07c901d..a04f8b7 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
@@ -27,6 +27,27 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
     final DefaultBuildLauncher launcher = new DefaultBuildLauncher(protocolConnection, parameters)
 
     def buildDelegatesToProtocolConnection() {
+        ResultHandler<Void> handler = Mock()
+
+        when:
+        launcher.run(handler)
+
+        then:
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
+            def params = args[1]
+            assert params.tasks == []
+            assert params.standardOutput == null
+            assert params.standardError == null
+            assert params.progressListener != null
+            def wrappedHandler = args[2]
+            wrappedHandler.onComplete(null)
+        }
+        1 * handler.onComplete(null)
+        0 * protocolConnection._
+        0 * handler._
+    }
+
+    def canConfigureTheOperation() {
         Task task1 = task(':task1')
         Task task2 = task(':task2')
         ResultHandler<Void> handler = Mock()
@@ -35,10 +56,9 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
         launcher.forTasks(task1, task2).run(handler)
 
         then:
-        1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
-            def buildParams = args[0]
-            assert buildParams.tasks == [':task1', ':task2']
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
             def params = args[1]
+            assert params.tasks == [':task1', ':task2']
             assert params.standardOutput == null
             assert params.standardError == null
             assert params.progressListener != null
@@ -61,7 +81,7 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
         launcher.run(handler)
 
         then:
-        1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
             def params = args[1]
             assert params.standardOutput == stdout
             assert params.standardError == stderr
@@ -78,7 +98,7 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
         launcher.run(handler)
 
         then:
-        1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
             def wrappedHandler = args[2]
             wrappedHandler.onFailure(failure)
         }
@@ -102,7 +122,7 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
         }
 
         then:
-        1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
             def handler = args[2]
             supplyResult.callbackLater {
                 handler.onComplete(null)
@@ -123,7 +143,7 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
         then:
         GradleConnectionException e = thrown()
         e.cause == failure
-        1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(Void.class, !null, !null) >> { args ->
             def handler = args[2]
             supplyResult.callbackLater {
                 handler.onFailure(failure)
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 f08b101..6e97958 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
@@ -18,7 +18,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.internal.consumer.protocoladapter.ModelPropertyHandler
+import org.gradle.tooling.internal.consumer.protocoladapter.ConsumerPropertyHandler
 import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter
 import org.gradle.tooling.internal.protocol.ProjectVersion3
 import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
@@ -31,11 +31,10 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
     final ProtocolToModelAdapter adapter = Mock()
     final ConnectionParameters parameters = Mock()
     final DefaultModelBuilder<GradleProject, ProjectVersion3> builder = new DefaultModelBuilder<GradleProject, ProjectVersion3>(GradleProject, ProjectVersion3, protocolConnection, adapter, parameters)
-    final ModelPropertyHandler modelPropertyHandler = Mock()
 
     def getModelDelegatesToProtocolConnectionToFetchModel() {
-        ResultHandler<GradleProject> handler = Mock()
         ResultHandlerVersion1<ProjectVersion3> adaptedHandler
+        ResultHandler<GradleProject> handler = Mock()
         ProjectVersion3 result = Mock()
         GradleProject adaptedResult = Mock()
 
@@ -43,11 +42,41 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         builder.get(handler)
 
         then:
-        1 * protocolConnection.getModel(ProjectVersion3, !null, !null) >> {args ->
+        1 * protocolConnection.run(ProjectVersion3, !null, !null) >> {args ->
+            def params = args[1]
+            assert params.standardOutput == null
+            assert params.standardError == null
+            assert params.progressListener != null
+            assert params.tasks == null
+            adaptedHandler = args[2]
+        }
+
+        when:
+        adaptedHandler.onComplete(result)
+
+        then:
+        1 * protocolConnection.versionDetails
+        1 * adapter.adapt(GradleProject.class, result, _ as ConsumerPropertyHandler) >> adaptedResult
+        1 * handler.onComplete(adaptedResult)
+        0 * _._
+    }
+
+    def canConfigureTheOperation() {
+        ResultHandler<GradleProject> handler = Mock()
+        ResultHandlerVersion1<ProjectVersion3> adaptedHandler
+        ProjectVersion3 result = Mock()
+        GradleProject adaptedResult = Mock()
+
+        when:
+        builder.forTasks('a', 'b').get(handler)
+
+        then:
+        1 * protocolConnection.run(ProjectVersion3, !null, !null) >> {args ->
             def params = args[1]
             assert params.standardOutput == null
             assert params.standardError == null
             assert params.progressListener != null
+            assert params.tasks == ['a', 'b']
             adaptedHandler = args[2]
         }
 
@@ -56,7 +85,7 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
 
         then:
         1 * protocolConnection.versionDetails
-        1 * adapter.adapt(GradleProject.class, result, _ as ModelPropertyHandler) >> adaptedResult
+        1 * adapter.adapt(GradleProject.class, result, _ as ConsumerPropertyHandler) >> adaptedResult
         1 * handler.onComplete(adaptedResult)
         0 * _._
     }
@@ -71,7 +100,7 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         builder.get(handler)
 
         then:
-        1 * protocolConnection.getModel(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
+        1 * protocolConnection.run(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
 
         when:
         adaptedHandler.onFailure(failure)
@@ -94,7 +123,7 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         builder.get(handler)
 
         then:
-        1 * protocolConnection.getModel(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
+        1 * protocolConnection.run(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
 
         when:
         adaptedHandler.onFailure(failure)
@@ -119,7 +148,7 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
 
         then:
         model == adaptedResult
-        1 * protocolConnection.getModel(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(!null, !null, !null) >> { args ->
             def handler = args[2]
             supplyResult.callbackLater {
                 handler.onComplete(result)
@@ -140,7 +169,7 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         then:
         GradleConnectionException e = thrown()
         e.cause.is(failure)
-        1 * protocolConnection.getModel(!null, !null, !null) >> { args ->
+        1 * protocolConnection.run(!null, !null, !null) >> { args ->
             def handler = args[2]
             supplyResult.callbackLater {
                 handler.onFailure(failure)
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 fe8c9c5..fe93200 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
@@ -22,6 +22,10 @@ interface TestModel {
 
     TestProject getProject()
 
+    boolean isConfigSupported()
+
+    String getConfig(String defaultValue)
+
     DomainObjectSet<? extends TestProject> getChildren()
 }
 
@@ -35,6 +39,8 @@ interface TestProtocolModel {
     TestProtocolProject getProject()
 
     Iterable<? extends TestProtocolProject> getChildren()
+
+    String getConfig();
 }
 
 interface PartialTestProtocolModel {
@@ -44,3 +50,19 @@ interface PartialTestProtocolModel {
 interface TestProtocolProject {
     String getName()
 }
+
+class ConfigMixin {
+    TestModel model
+
+    ConfigMixin(TestModel model) {
+        this.model = model
+    }
+
+    String getConfig(String value) {
+        return "[${model.getConfig(value)}]"
+    }
+
+    String getName() {
+        return "[${model.name}]"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/AdaptedConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/AdaptedConnectionTest.groovy
new file mode 100644
index 0000000..c15308f
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/AdaptedConnectionTest.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.tooling.internal.consumer.connection
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.protocol.ConnectionVersion4
+import org.gradle.tooling.internal.protocol.ProjectVersion3
+import spock.lang.Specification
+
+class AdaptedConnectionTest extends Specification {
+    final ConnectionVersion4 target = Mock()
+    final ConsumerOperationParameters parameters = Mock()
+    final AdaptedConnection connection = new AdaptedConnection(target)
+
+    def "builds model using getModel() method"() {
+        ProjectVersion3 model = Mock()
+
+        when:
+        def result = connection.run(ProjectVersion3.class, parameters)
+
+        then:
+        result == model
+
+        and:
+        1 * target.getModel(ProjectVersion3.class, parameters) >> model
+        0 * target._
+    }
+
+    def "runs build using executeBuild() method"() {
+        when:
+        connection.run(Void.class, parameters)
+
+        then:
+        1 * target.executeBuild(parameters, parameters)
+        0 * target._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnectionTest.groovy
new file mode 100644
index 0000000..0e5ffd8
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/BuildActionRunnerBackedConsumerConnectionTest.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.tooling.internal.consumer.connection
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.protocol.BuildActionRunner
+import org.gradle.tooling.internal.protocol.BuildResult
+import org.gradle.tooling.internal.protocol.ConfigurableConnection
+import org.gradle.tooling.internal.protocol.ConnectionVersion4
+import spock.lang.Specification
+
+class BuildActionRunnerBackedConsumerConnectionTest extends Specification {
+    final TestBuildActionRunner target = Mock()
+    final ConsumerOperationParameters parameters = Mock()
+    final BuildActionRunnerBackedConsumerConnection connection = new BuildActionRunnerBackedConsumerConnection(target)
+
+    def "configures connection"() {
+        def parameters = new ConsumerConnectionParameters(false)
+
+        when:
+        connection.configure(parameters)
+
+        then:
+        1 * target.configure(parameters)
+    }
+
+    def "builds model using run() method"() {
+        BuildResult<String> result = Mock()
+
+        given:
+        result.model >> 'ok'
+
+        when:
+        def model = connection.run(String.class, parameters)
+
+        then:
+        model == 'ok'
+
+        and:
+        1 * target.run(String.class, parameters) >> result
+        0 * target._
+    }
+
+    interface TestBuildActionRunner extends ConnectionVersion4, BuildActionRunner, ConfigurableConnection {
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnectionTest.groovy
new file mode 100644
index 0000000..d5c0bf3
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/InternalConnectionBackedConsumerConnectionTest.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.tooling.internal.consumer.connection
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.protocol.InternalConnection
+import spock.lang.Specification
+
+class InternalConnectionBackedConsumerConnectionTest extends Specification {
+    final InternalConnection target = Mock()
+    final ConsumerOperationParameters parameters = Mock()
+    final InternalConnectionBackedConsumerConnection connection = new InternalConnectionBackedConsumerConnection(target)
+
+    def "builds model using getTheModel() method"() {
+        when:
+        def result = connection.run(String.class, parameters)
+
+        then:
+        result == 'ok'
+
+        and:
+        1 * target.getTheModel(String.class, parameters) >> 'ok'
+        0 * target._
+    }
+
+    def "runs build using executeBuild() method"() {
+        when:
+        connection.run(Void.class, parameters)
+
+        then:
+        1 * target.executeBuild(parameters, parameters)
+        0 * target._
+    }
+}
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
index d01594e..194a7b5 100644
--- 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
@@ -20,16 +20,15 @@ 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.ConsumerConnectionParameters
 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 ConsumerConnectionParameters connectionParams = Mock()
     final ConsumerConnection consumerConnection = Mock()
     final LoggingProvider loggingProvider = Mock()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
@@ -38,70 +37,43 @@ class LazyConnectionTest extends Specification {
     static class SomeModel {}
 
     def setup() {
+        connection.connectionParameters = connectionParams
         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)
+        connection.run(SomeModel, params)
 
         then:
         1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> 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)
+        connection.run(SomeModel, params)
+        connection.run(String, params)
 
         then:
         1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
         1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
-        1 * consumerConnection.executeBuild(buildParams, params)
-        2 * connection.featureValidator.validate(consumerConnection, params)
+        1 * connection.modelProvider.provide(consumerConnection, String, params)
         0 * _._
     }
 
     def stopsConnectionOnStop() {
         when:
-        connection.getModel(SomeModel, params)
+        connection.run(SomeModel, params)
         connection.stop()
 
         then:
         1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> consumerConnection
         1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
-        1 * connection.featureValidator.validate(consumerConnection, params)
         1 * consumerConnection.stop()
         0 * _._
     }
@@ -118,13 +90,13 @@ class LazyConnectionTest extends Specification {
         def failure = new RuntimeException()
 
         when:
-        connection.getModel(SomeModel, params)
+        connection.run(SomeModel, params)
 
         then:
         RuntimeException e = thrown()
         e == failure
         1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
-        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> { throw failure }
+        1 * implementationLoader.create(distribution, progressLoggerFactory, connectionParams) >> { throw failure }
 
         when:
         connection.stop()
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
index c32a709..5c60bd2 100644
--- 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
@@ -20,7 +20,6 @@ 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
 
@@ -38,36 +37,16 @@ class ProgressLoggingConnectionTest extends Specification {
 
     def notifiesProgressListenerOfStartAndEndOfFetchingModel() {
         when:
-        connection.getModel(SomeModel, params)
+        connection.run(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.setDescription('Build')
         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 * target.run(SomeModel, params)
         1 * progressLogger.completed()
         1 * listenerManager.removeListener(!null)
         _ * params.progressListener >> listener
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
index fad0b4a..553949e 100644
--- 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
@@ -18,12 +18,14 @@ 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 org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
 import spock.lang.Specification
 import org.gradle.internal.classpath.DefaultClassPath
 
 class CachingToolingImplementationLoaderTest extends Specification {
     final ToolingImplementationLoader target = Mock()
     final ProgressLoggerFactory loggerFactory = Mock()
+    final ConsumerConnectionParameters params = Mock()
     final CachingToolingImplementationLoader loader = new CachingToolingImplementationLoader(target)
 
     def delegatesToTargetLoaderToCreateImplementation() {
@@ -31,11 +33,11 @@ class CachingToolingImplementationLoaderTest extends Specification {
         final ConsumerConnection connection = Mock()
 
         when:
-        def impl = loader.create(distribution, loggerFactory, true)
+        def impl = loader.create(distribution, loggerFactory, params)
 
         then:
         impl == connection
-        1 * target.create(distribution, loggerFactory, true) >> connection
+        1 * target.create(distribution, loggerFactory, params) >> connection
         _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('a.jar'))
         0 * _._
     }
@@ -45,13 +47,13 @@ class CachingToolingImplementationLoaderTest extends Specification {
         final ConsumerConnection connection = Mock()
 
         when:
-        def impl = loader.create(distribution, loggerFactory, true)
-        def impl2 = loader.create(distribution, loggerFactory, true)
+        def impl = loader.create(distribution, loggerFactory, params)
+        def impl2 = loader.create(distribution, loggerFactory, params)
 
         then:
         impl == connection
         impl2 == connection
-        1 * target.create(distribution, loggerFactory, true) >> connection
+        1 * target.create(distribution, loggerFactory, params) >> connection
         _ * distribution.getToolingImplementationClasspath(loggerFactory) >> { new DefaultClassPath(new File('a.jar')) }
         0 * _._
     }
@@ -63,14 +65,14 @@ class CachingToolingImplementationLoaderTest extends Specification {
         Distribution distribution2 = Mock()
 
         when:
-        def impl = loader.create(distribution1, loggerFactory, true)
-        def impl2 = loader.create(distribution2, loggerFactory, false)
+        def impl = loader.create(distribution1, loggerFactory, params)
+        def impl2 = loader.create(distribution2, loggerFactory, params)
 
         then:
         impl == connection1
         impl2 == connection2
-        1 * target.create(distribution1, loggerFactory, true) >> connection1
-        1 * target.create(distribution2, loggerFactory, false) >> connection2
+        1 * target.create(distribution1, loggerFactory, params) >> connection1
+        1 * target.create(distribution2, loggerFactory, params) >> connection2
         _ * distribution1.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('a.jar'))
         _ * distribution2.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('b.jar'))
         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
index 2023a59..5a01685 100644
--- 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
@@ -15,44 +15,60 @@
  */
 package org.gradle.tooling.internal.consumer.loader
 
+import org.gradle.internal.classpath.DefaultClassPath
 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.tooling.internal.consumer.connection.AdaptedConnection
+import org.gradle.tooling.internal.consumer.connection.BuildActionRunnerBackedConsumerConnection
+import org.gradle.tooling.internal.consumer.connection.InternalConnectionBackedConsumerConnection
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters
+import org.gradle.tooling.internal.protocol.*
 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
-import org.gradle.internal.classpath.DefaultClassPath
 
 class DefaultToolingImplementationLoaderTest extends Specification {
     @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
     Distribution distribution = Mock()
     ProgressLoggerFactory loggerFactory = Mock()
 
-    def usesMetaInfServiceToDetermineFactoryImplementation() {
+    def "locates connection implementation using meta-inf service then instantiates and configures the connection"() {
         given:
         def loader = new DefaultToolingImplementationLoader()
         distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(
-                getToolingApiResourcesDir(),
+                getToolingApiResourcesDir(connectionImplementation),
                 ClasspathUtil.getClasspathForClass(TestConnection.class),
                 ClasspathUtil.getClasspathForClass(ActorFactory.class),
                 ClasspathUtil.getClasspathForClass(Logger.class),
+                ClasspathUtil.getClasspathForClass(GroovyObject.class),
                 getVersionResourcesDir(),
                 ClasspathUtil.getClasspathForClass(GradleVersion.class))
 
         when:
-        def adaptedConnection = loader.create(distribution, loggerFactory, true)
+        def adaptedConnection = loader.create(distribution, loggerFactory, new ConsumerConnectionParameters(true))
 
         then:
-        adaptedConnection.delegate.class != TestConnection.class //different classloaders
-        adaptedConnection.delegate.class.name == TestConnection.class.name
+        adaptedConnection.delegate.class != connectionImplementation //different classloaders
+        adaptedConnection.delegate.class.name == connectionImplementation.name
+        adaptedConnection.delegate.configured
+
+        and:
+        adaptedConnection.class == adapter
+
+        where:
+        connectionImplementation      | adapter
+        TestConnection.class          | BuildActionRunnerBackedConsumerConnection.class
+        TestOldConnection.class       | InternalConnectionBackedConsumerConnection.class
+        TestEvenOlderConnection.class | AdaptedConnection.class
     }
 
-    private getToolingApiResourcesDir() {
-        tmpDir.file("META-INF/services/org.gradle.tooling.internal.protocol.ConnectionVersion4") << TestConnection.name
+    private getToolingApiResourcesDir(Class implementation) {
+        tmpDir.file("META-INF/services/org.gradle.tooling.internal.protocol.ConnectionVersion4") << implementation.name
         return tmpDir.dir;
     }
 
@@ -65,7 +81,7 @@ class DefaultToolingImplementationLoaderTest extends Specification {
         def loader = new DefaultToolingImplementationLoader(cl)
 
         when:
-        loader.create(distribution, loggerFactory, true)
+        loader.create(distribution, loggerFactory, new ConsumerConnectionParameters(true))
 
         then:
         UnsupportedVersionException e = thrown()
@@ -73,4 +89,84 @@ class DefaultToolingImplementationLoaderTest extends Specification {
         _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath()
         _ * distribution.displayName >> '<dist-display-name>'
     }
-}
\ No newline at end of file
+}
+
+class TestConnection implements ConnectionVersion4, BuildActionRunner, ConfigurableConnection {
+    boolean configured
+
+    void configure(ConnectionParameters parameters) {
+        configured = parameters.verboseLogging
+    }
+
+    def <T> BuildResult<T> run(Class<T> type, BuildParameters parameters) {
+        throw new UnsupportedOperationException()
+    }
+
+    void stop() {
+        throw new UnsupportedOperationException()
+    }
+
+    ConnectionMetaDataVersion1 getMetaData() {
+        throw new UnsupportedOperationException()
+    }
+
+    ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+
+    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+}
+
+class TestOldConnection implements InternalConnection {
+    boolean configured
+
+    void configureLogging(boolean verboseLogging) {
+        configured = verboseLogging
+    }
+
+    def <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+
+    void stop() {
+        throw new UnsupportedOperationException()
+    }
+
+    ConnectionMetaDataVersion1 getMetaData() {
+        throw new UnsupportedOperationException()
+    }
+
+    ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+
+    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+}
+
+class TestEvenOlderConnection implements ConnectionVersion4 {
+    boolean configured
+
+    void configureLogging(boolean verboseLogging) {
+        configured = verboseLogging
+    }
+
+    void stop() {
+        throw new UnsupportedOperationException()
+    }
+
+    ConnectionMetaDataVersion1 getMetaData() {
+        throw new UnsupportedOperationException()
+    }
+
+    ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+
+    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
+        throw new UnsupportedOperationException()
+    }
+}
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
index 247bb9c..39932ef 100644
--- 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
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package org.gradle.tooling.internal.consumer.loader;
+package org.gradle.tooling.internal.consumer.loader
 
+import org.gradle.tooling.internal.consumer.parameters.ConsumerConnectionParameters;
 
 import java.util.concurrent.locks.Lock
 import java.util.concurrent.locks.ReentrantLock
@@ -33,6 +34,7 @@ public class SynchronizedToolingImplementationLoaderTest extends Specification {
     def factory = Mock(ProgressLoggerFactory)
     def distro = Mock(Distribution)
     def logger = Mock(ProgressLogger)
+    def params = Mock(ConsumerConnectionParameters)
 
     def loader = new SynchronizedToolingImplementationLoader(Mock(ToolingImplementationLoader))
 
@@ -42,7 +44,7 @@ public class SynchronizedToolingImplementationLoaderTest extends Specification {
 
     def "reports progress when busy"() {
         when:
-        loader.create(distro, factory, true)
+        loader.create(distro, factory, params)
 
         then: "stubs"
         1 * loader.lock.tryLock() >> false
@@ -55,7 +57,7 @@ public class SynchronizedToolingImplementationLoaderTest extends Specification {
         then:
         1 * loader.lock.lock()
         then:
-        1 * loader.delegate.create(distro, factory, true)
+        1 * loader.delegate.create(distro, factory, params)
         then:
         1 * logger.completed()
         1 * loader.lock.unlock()
@@ -64,12 +66,12 @@ public class SynchronizedToolingImplementationLoaderTest extends Specification {
 
     def "does not report progress when appropriate"() {
         when:
-        loader.create(distro, factory, true)
+        loader.create(distro, factory, params)
 
         then:
         1 * loader.lock.tryLock() >> true
         then:
-        1 * loader.delegate.create(distro, factory, true)
+        1 * loader.delegate.create(distro, factory, params)
         then:
         1 * loader.lock.unlock()
         0 * _
@@ -84,7 +86,7 @@ public class SynchronizedToolingImplementationLoaderTest extends Specification {
 
         when:
         5.times {
-            concurrent.start { loader.create(distro, factory, true) }
+            concurrent.start { loader.create(distro, factory, params) }
         }
 
         then:
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
deleted file mode 100644
index 8a449c0..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/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.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/protocoladapter/ProtocolToModelAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapterTest.groovy
index f85c173..add0d55 100644
--- 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
@@ -31,22 +31,21 @@ import org.gradle.tooling.internal.consumer.*
  */
 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
+        adapter.adapt(TestModel.class, protocolModel) 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)
+        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)
@@ -58,7 +57,7 @@ class ProtocolToModelAdapterTest extends Specification {
         _ * protocolModel.getName() >> 'name'
 
         expect:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.name == 'name'
     }
 
@@ -69,7 +68,7 @@ class ProtocolToModelAdapterTest extends Specification {
         _ * protocolProject.getName() >> 'name'
 
         expect:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.project instanceof TestProject
         model.project.name == 'name'
     }
@@ -79,7 +78,7 @@ class ProtocolToModelAdapterTest extends Specification {
         _ * protocolModel.getProject() >> null
 
         expect:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.project == null
     }
 
@@ -90,7 +89,7 @@ class ProtocolToModelAdapterTest extends Specification {
         _ * protocolProject.getName() >> 'name'
 
         expect:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.children.size() == 1
         model.children[0] instanceof TestProject
         model.children[0].name == 'name'
@@ -104,7 +103,7 @@ class ProtocolToModelAdapterTest extends Specification {
         _ * protocolProject.getName() >> 'name'
 
         expect:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.project.is(model.project)
         model.children.is(model.children)
     }
@@ -113,7 +112,7 @@ class ProtocolToModelAdapterTest extends Specification {
         PartialTestProtocolModel protocolModel = Mock()
 
         when:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.project
 
         then:
@@ -126,7 +125,7 @@ class ProtocolToModelAdapterTest extends Specification {
         RuntimeException failure = new RuntimeException()
 
         when:
-        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        def model = adapter.adapt(TestModel.class, protocolModel)
         model.name
 
         then:
@@ -135,13 +134,135 @@ class ProtocolToModelAdapterTest extends Specification {
         e == failure
     }
 
+    def isPropertySupportedMethodReturnsTrueWhenProtocolObjectHasAssociatedProperty() {
+        TestProtocolModel protocolModel = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+
+        then:
+        model.configSupported
+    }
+
+    def isPropertySupportedMethodReturnsFalseWhenProtocolObjectDoesNotHaveAssociatedProperty() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+
+        then:
+        !model.configSupported
+    }
+
+    def safeGetterDelegatesToProtocolObject() {
+        TestProtocolModel protocolModel = Mock()
+
+        given:
+        protocolModel.config >> "value"
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+
+        then:
+        model.getConfig("default") == "value"
+    }
+
+    def safeGetterDelegatesReturnsDefaultValueWhenProtocolObjectDoesNotHaveAssociatedProperty() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+
+        then:
+        model.getConfig("default") == "default"
+    }
+
+    def safeGetterDelegatesReturnsDefaultValueWhenPropertyValueIsNull() {
+        TestProtocolModel protocolModel = Mock()
+
+        given:
+        protocolModel.config >> null
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel)
+
+        then:
+        model.getConfig("default") == "default"
+    }
+
+    def methodInvokerCanOverrideGetterMethod() {
+        MethodInvoker propertyHandler = Mock()
+        TestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        given:
+        propertyHandler.invoke({it.name == 'getProject'}) >> { MethodInvocation method -> method.result = project }
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+
+        then:
+        model.project == project
+
+        and:
+        0 * protocolModel._
+    }
+
+    def methodInvokerCanProvideGetterMethodImplementation() {
+        MethodInvoker propertyHandler = Mock()
+        PartialTestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        given:
+        propertyHandler.invoke({it.name == 'getProject'}) >> { MethodInvocation method -> method.result = project }
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+
+        then:
+        model.project == project
+
+        and:
+        0 * protocolModel._
+    }
+
+    def methodInvokerPropertiesAreCached() {
+        MethodInvoker propertyHandler = Mock()
+        PartialTestProtocolModel protocolModel = Mock()
+        TestProject project = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.project
+        model.project
+
+        then:
+        1 * propertyHandler.invoke(!null) >> { MethodInvocation method -> method.result = project }
+        0 * propertyHandler._
+        0 * protocolModel._
+    }
+
+    def canMixInMethodsFromAnotherBean() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        given:
+        protocolModel.name >> 'name'
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, ConfigMixin)
+
+        then:
+        model.name == "[name]"
+        model.getConfig('default') == "[default]"
+    }
+
     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)
+        def library = adapter.adapt(IdeaDependency.class, libraryDep)
+        def module  = adapter.adapt(IdeaDependency.class, moduleDep)
 
         then:
         library instanceof IdeaSingleEntryLibraryDependency
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index f4e5db5..94de843 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -15,7 +15,7 @@ dependencies {
 useTestFixtures()
 
 integTestTasks.all {
-    dependsOn ':publishLocalArchives', ':binZip'
+    dependsOn({ rootProject.getTasksByName('publishLocalArchives', true) }, ':distributions:binZip')
 
     doFirst {
         systemProperties['org.gradle.integtest.toolingApiFromTestClasspath'] = 'true'
@@ -25,3 +25,13 @@ integTestTasks.all {
 daemonIntegTest {
     enabled = false //tooling integ tests use daemon anyway, don't rerun
 }
+
+eclipse {
+	classpath {
+    	file.whenMerged { classpath ->
+        	 //**TODO
+        	classpath.entries.removeAll { it.path.contains('src/test/groovy') }
+        	classpath.entries.removeAll { it.path.contains('src/integTest/groovy') }
+        }
+    }
+}
diff --git a/subprojects/ui/ui.gradle b/subprojects/ui/ui.gradle
index 32aa5af..0683f61 100644
--- a/subprojects/ui/ui.gradle
+++ b/subprojects/ui/ui.gradle
@@ -27,8 +27,4 @@ dependencies {
     runtime libraries.jaxen
 }
 
-test {
-    jvmArgs '-Xms128m', '-Xmx256m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
-}
-
 useTestFixtures()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
rename to subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
diff --git a/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
new file mode 100644
index 0000000..1552469
--- /dev/null
+++ b/subprojects/wrapper/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * 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.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.test.fixtures.server.http.TestProxyServer
+import org.gradle.util.GradleVersion
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TextUtil
+import org.junit.Rule
+import spock.lang.Issue
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+import static org.hamcrest.Matchers.containsString
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+class WrapperProjectIntegrationTest extends AbstractIntegrationSpec {
+    @Rule HttpServer server = new HttpServer()
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+
+    void setup() {
+        server.start()
+        server.expectUserAgent(matchesNameAndVersion("gradlew", GradleVersion.current().getVersion()))
+    }
+
+    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.allowGetOrHead("/gradlew/dist", distribution.binDistribution)
+    }
+
+    public void "has non-zero exit code on build failure"() {
+        given:
+        prepareWrapper("http://localhost:${server.port}")
+
+        expect:
+        server.allowGetOrHead("/gradlew/dist", distribution.binDistribution)
+
+        when:
+        ExecutionFailure failure = wrapperExecuter.withTasks('unknown').runWithFailure()
+
+        then:
+        failure.assertHasDescription("Task 'unknown' not found in root project")
+    }
+
+    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'))
+    }
+
+    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}
+"""
+
+        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/wrapper/src/main/java/org/gradle/wrapper/Download.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
index 8e661b7..e0d2210 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
@@ -17,11 +17,7 @@
 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;
+import java.net.*;
 
 /**
  * @author Hans Dockter
@@ -29,8 +25,12 @@ import java.net.URLConnection;
 public class Download implements IDownload {
     private static final int PROGRESS_CHUNK = 20000;
     private static final int BUFFER_SIZE = 10000;
+    private final String applicationName;
+    private final String applicationVersion;
 
-    public Download() {
+    public Download(String applicationName, String applicationVersion) {
+        this.applicationName = applicationName;
+        this.applicationVersion = applicationVersion;
         configureProxyAuthentication();
     }
 
@@ -49,15 +49,17 @@ public class Download implements IDownload {
         downloadInternal(address, destination);
     }
 
-    private void downloadInternal(URI address, File destination) throws Exception {
+    private void downloadInternal(URI address, File destination)
+            throws Exception {
         OutputStream out = null;
         URLConnection conn;
         InputStream in = null;
         try {
             URL url = address.toURL();
-            out = new BufferedOutputStream(
-                    new FileOutputStream(destination));
+            out = new BufferedOutputStream(new FileOutputStream(destination));
             conn = url.openConnection();
+            final String userAgentValue = calculateUserAgent();
+            conn.setRequestProperty("User-Agent", userAgentValue);
             in = conn.getInputStream();
             byte[] buffer = new byte[BUFFER_SIZE];
             int numRead;
@@ -81,13 +83,25 @@ public class Download implements IDownload {
         }
     }
 
-    private static class SystemPropertiesProxyAuthenticator extends Authenticator {
+    private String calculateUserAgent() {
+        String appVersion = applicationVersion;
+
+        String javaVendor = System.getProperty("java.vendor");
+        String javaVersion = System.getProperty("java.version");
+        String javaVendorVersion = System.getProperty("java.vm.version");
+        String osName = System.getProperty("os.name");
+        String osVersion = System.getProperty("os.version");
+        String osArch = System.getProperty("os.arch");
+        return String.format("%s/%s (%s;%s;%s) (%s;%s;%s)", applicationName, appVersion, osName, osVersion, osArch, javaVendor, javaVersion, javaVendorVersion);
+    }
+
+    private static class SystemPropertiesProxyAuthenticator extends
+            Authenticator {
         @Override
         protected PasswordAuthentication getPasswordAuthentication() {
             return new PasswordAuthentication(
-                    System.getProperty("http.proxyUser"),
-                    System.getProperty("http.proxyPassword", "").toCharArray());
+                    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 7f40a52..bb03dbb 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
@@ -20,6 +20,7 @@ import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.SystemPropertiesCommandLineConverter;
 
 import java.io.File;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Map;
@@ -40,13 +41,13 @@ public class GradleWrapperMain {
 
         Properties systemProperties = System.getProperties();
         systemProperties.putAll(parseSystemPropertiesFromArgs(args));
-        
+
         addSystemProperties(rootDir);
 
         WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out);
         wrapperExecutor.execute(
                 args,
-                new Install(new Download(), new PathAssembler(gradleUserHome())),
+                new Install(new Download("gradlew", wrapperVersion()), new PathAssembler(gradleUserHome())),
                 new BootstrapMainStarter());
     }
 
@@ -57,7 +58,7 @@ public class GradleWrapperMain {
         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(rootDir, "gradle.properties")));
@@ -84,11 +85,33 @@ public class GradleWrapperMain {
         return new File(location.getPath());
     }
 
+    static String wrapperVersion() {
+        try {
+            InputStream resourceAsStream = GradleWrapperMain.class.getResourceAsStream("/build-receipt.properties");
+            if (resourceAsStream == null) {
+                throw new RuntimeException("No build receipt resource found.");
+            }
+            Properties buildReceipt = new Properties();
+            try {
+                buildReceipt.load(resourceAsStream);
+                String versionNumber = buildReceipt.getProperty("versionNumber");
+                if (versionNumber == null) {
+                    throw new RuntimeException("No version number specified in build receipt resource.");
+                }
+                return versionNumber;
+            } finally {
+                resourceAsStream.close();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Could not determine wrapper version.", e);
+        }
+    }
+
     private static File gradleUserHome() {
         String gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY);
         if (gradleUserHome != null) {
             return new File(gradleUserHome);
-        } else if((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) != null) {
+        } else if ((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) != null) {
             return new File(gradleUserHome);
         } else {
             return new File(DEFAULT_GRADLE_USER_HOME);
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
index 70fd784..4b3b90e 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
@@ -20,7 +20,8 @@ import org.gradle.util.TemporaryFolder
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import static org.junit.Assert.*
+
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
@@ -35,8 +36,8 @@ class DownloadTest {
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
 
-    @Before public void setUp()  {
-        download = new Download()
+    @Before public void setUp() {
+        download = new Download("gradlew", "aVersion")
         testDir = tmpDir.dir
         rootDir = new File(testDir, 'root')
         downloadFile = new File(rootDir, 'file')
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/GradleWrapperMainTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/GradleWrapperMainTest.groovy
new file mode 100644
index 0000000..b661146
--- /dev/null
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/GradleWrapperMainTest.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.wrapper
+
+import org.gradle.util.GradleVersion
+import spock.lang.Specification
+
+class GradleWrapperMainTest extends Specification {
+    def "uses build receipt to determine version"() {
+        expect:
+        GradleWrapperMain.wrapperVersion() == GradleVersion.current().version
+    }
+}
diff --git a/subprojects/wrapper/wrapper.gradle b/subprojects/wrapper/wrapper.gradle
index 44a2582..e03ed74 100644
--- a/subprojects/wrapper/wrapper.gradle
+++ b/subprojects/wrapper/wrapper.gradle
@@ -13,19 +13,30 @@
  * 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(":cli")
     testCompile libraries.ant
+
+    integTestRuntime rootProject.configurations.testRuntime.allDependencies
+}
+
+task buildReceiptResource(type: Copy, dependsOn: rootProject.createBuildReceipt) {
+    into generatedTestResourcesDir
+    from rootProject.createBuildReceipt.receiptFile
 }
+sourceSets.test.output.dir generatedTestResourcesDir, builtBy: buildReceiptResource
 
 task executableJar(type: Jar) {
     archiveName = 'gradle-wrapper.jar'
     from sourceSets.main.output
+    from rootProject.createBuildReceipt
     from configurations.runtime.allDependencies.withType(ProjectDependency).collect { it.dependencyProject.sourceSets.main.output }
 }
 
-useTestFixtures()
+integTestTasks.all {
+    inputs.files { project(":distributions").binZip }
+}
 
+useTestFixtures()
+useClassycle()

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